diff --git a/deno.lock b/deno.lock
index 55a3dca..1ea3a90 100644
--- a/deno.lock
+++ b/deno.lock
@@ -89,33 +89,57 @@
"npm": {
"@deno/darwin-arm64@2.6.4": {
"integrity": "sha512-5pocuV2IVWYUzomXze+bDSO86uX1WPxUn82BS7BrLI3ix8Eq7l8DhvYgz064w7rwCM4CiLLSc6MAQpAI+0gUag==",
- "os": ["darwin"],
- "cpu": ["arm64"]
+ "os": [
+ "darwin"
+ ],
+ "cpu": [
+ "arm64"
+ ]
},
"@deno/darwin-x64@2.6.4": {
"integrity": "sha512-tX7Fz47HcoXTYR4wN2WzXjqJtEPTftzjEknCYkZa8D4BzvpN7Zo1ra3Jd6bWPN9+T6mgTL6YMPMMSigXddIUcA==",
- "os": ["darwin"],
- "cpu": ["x64"]
+ "os": [
+ "darwin"
+ ],
+ "cpu": [
+ "x64"
+ ]
},
"@deno/linux-arm64-glibc@2.6.4": {
"integrity": "sha512-wY4hjjVLq5idClWj6e1de9IUZ0QZ1rxo16o0dwCJpMi+w1lolxtYxGF4y7pkcxAdZOpMGsXZuYMPq1Z2n51esA==",
- "os": ["linux"],
- "cpu": ["arm64"]
+ "os": [
+ "linux"
+ ],
+ "cpu": [
+ "arm64"
+ ]
},
"@deno/linux-x64-glibc@2.6.4": {
"integrity": "sha512-H9b0WJw+eWyC6GcMGCXxC/3lz2kVveyWyUrzGyQ8hJmtDJpWiDnCU2iPqEpEcwXHgAUFFyjnRaFwedwwv5ApdA==",
- "os": ["linux"],
- "cpu": ["x64"]
+ "os": [
+ "linux"
+ ],
+ "cpu": [
+ "x64"
+ ]
},
"@deno/win32-arm64@2.6.4": {
"integrity": "sha512-fMH1dx54ORbCTpMorGjDshwonIu6KfgfPC+Pq+14jeq/8604EIFXiA484p3Ov+ViZycndNHEPe8ZKPNd8xLjJA==",
- "os": ["win32"],
- "cpu": ["arm64"]
+ "os": [
+ "win32"
+ ],
+ "cpu": [
+ "arm64"
+ ]
},
"@deno/win32-x64@2.6.4": {
"integrity": "sha512-hAxqNrBLHCINtqDgKcRiu8QtMbtdnHOVW3OFQjzA3gZEcpnGUK3JiF5ql++83SjuQH8ViZKGcdqkioLR7Bf14A==",
- "os": ["win32"],
- "cpu": ["x64"]
+ "os": [
+ "win32"
+ ],
+ "cpu": [
+ "x64"
+ ]
},
"@isaacs/fs-minipass@4.0.1": {
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
@@ -352,7 +376,8 @@
"dependencies": [
"jsr:@david/dax@~0.44.2",
"jsr:@openai/openai@^6.16.0",
- "jsr:@zod/zod@^4.3.5"
+ "jsr:@zod/zod@^4.3.5",
+ "jsr:@david/dax@~0.44.2"
],
"packageJson": {
"dependencies": [
diff --git a/diff.txt b/diff.txt
new file mode 100644
index 0000000..b2704fc
Binary files /dev/null and b/diff.txt differ
diff --git a/scripts/sql_para_local_como_remoto.ts b/scripts/sql_para_local_como_remoto.ts
index f01cdaf..4488a6e 100644
--- a/scripts/sql_para_local_como_remoto.ts
+++ b/scripts/sql_para_local_como_remoto.ts
@@ -4,7 +4,7 @@
*/
import { $ } from "@david/dax";
-const OUT_FILE = "supabase/diffs/sql_para_remoto_como_local.sql";
+const OUT_FILE = "supabase/diffs/sql_para_local_como_remoto.sql";
console.log("🔍 Comparando base de datos local vs. remota...");
diff --git a/supabase/.temp/cli-latest b/supabase/.temp/cli-latest
index 9b07af9..1dd6178 100644
--- a/supabase/.temp/cli-latest
+++ b/supabase/.temp/cli-latest
@@ -1 +1 @@
-v2.72.7
\ No newline at end of file
+v2.75.0
\ No newline at end of file
diff --git a/supabase/config.toml b/supabase/config.toml
index d81e000..5f119d7 100644
--- a/supabase/config.toml
+++ b/supabase/config.toml
@@ -26,3 +26,14 @@ entrypoint = "./functions/create-chat-conversation/index.ts"
# Specifies static files to be bundled with the function. Supports glob patterns.
# For example, if you want to serve static HTML pages in your function:
# static_files = [ "./functions/create-chat-conversation/*.html" ]
+
+[functions.ai-generate-subject]
+enabled = true
+verify_jwt = true
+import_map = "./functions/ai-generate-subject/deno.json"
+# Uncomment to specify a custom file path to the entrypoint.
+# Supported file extensions are: .ts, .js, .mjs, .jsx, .tsx
+entrypoint = "./functions/ai-generate-subject/index.ts"
+# Specifies static files to be bundled with the function. Supports glob patterns.
+# For example, if you want to serve static HTML pages in your function:
+# static_files = [ "./functions/ai-generate-subject/*.html" ]
diff --git a/supabase/data_backup.sql b/supabase/data_backup.sql
index 8e835b7..8118592 100644
--- a/supabase/data_backup.sql
+++ b/supabase/data_backup.sql
@@ -1924,7 +1924,17 @@ INSERT INTO "public"."usuarios_app" ("id", "nombre_completo", "email", "externo"
INSERT INTO "public"."facultades" ("id", "nombre", "nombre_corto", "color", "icono", "creado_en", "actualizado_en") VALUES
('155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'Facultad de Ingeniería', 'ING', '#EF4444', 'Hammer', '2026-01-09 13:45:37.784586+00', '2026-01-09 21:31:25.552784+00'),
- ('45a6da79-1e2d-4854-9953-6229f46c8e82', 'Facultad de Negocios', 'NEG', '#2980B9', 'Briefcase', '2026-01-09 21:32:50.605959+00', '2026-01-09 21:32:50.605959+00');
+ ('45a6da79-1e2d-4854-9953-6229f46c8e82', 'Facultad de Negocios', 'NEG', '#2980B9', 'Briefcase', '2026-01-09 21:32:50.605959+00', '2026-01-09 21:32:50.605959+00'),
+ ('21561e2c-22be-40ec-b0ad-520b5253f846', 'Coordinación de Desarrollo Humano Profesional', 'CDHP', '#F472B6', 'HeartHandshake', '2026-01-30 20:38:02.844498+00', '2026-01-30 20:38:02.844498+00'),
+ ('cd9409f5-bbcd-466d-82eb-b206cea51b8b', 'Centro de Idiomas', 'CI', '#2DD4BF', 'Languages', '2026-01-30 20:38:02.844498+00', '2026-01-30 20:38:02.844498+00'),
+ ('ab73dbce-e750-4070-82b1-4e71347cd273', 'Escuela de Altos Estudios en Salud', 'EAES', '#F43F5E', 'Stethoscope', '2026-01-30 20:38:02.844498+00', '2026-01-30 20:38:02.844498+00'),
+ ('0d711469-4668-4910-b08e-88406ad30c9a', 'Facultad Mexicana de Arquitectura, Diseño y Comunicación', 'FAMADYC', '#EC4899', 'DraftingCompass', '2026-01-30 20:38:02.844498+00', '2026-01-30 20:38:02.844498+00'),
+ ('7a162523-1120-4fb9-b291-dfb606c8f1d4', 'Facultad de Derecho', 'DER', '#64748B', 'Scale', '2026-01-30 20:38:02.844498+00', '2026-01-30 20:38:02.844498+00'),
+ ('a977a640-709d-47d7-a306-9acbe4a867a9', 'Facultad de Humanidades y Ciencias Sociales', 'HUM', '#6366F1', 'Users', '2026-01-30 20:38:02.844498+00', '2026-01-30 20:38:02.844498+00'),
+ ('7884f606-71b0-4f67-92da-bf22e0601480', 'Facultad Mexicana de Medicina', 'MED', '#10B981', 'HeartPulse', '2026-01-30 20:38:02.844498+00', '2026-01-30 20:38:02.844498+00'),
+ ('d17b19c6-b7ab-4bdc-8bb9-ddf6f9e0358e', 'Facultad de Ciencias Químicas', 'FCQ', '#84CC16', 'FlaskConical', '2026-01-30 20:38:02.844498+00', '2026-01-30 20:38:02.844498+00'),
+ ('d8e1f958-6a3d-4a19-afa8-597755ea7873', 'Escuela de Ciencias Religiosas', 'ECR', '#0EA5E9', 'BookHeart', '2026-01-30 20:38:02.844498+00', '2026-01-30 20:38:02.844498+00'),
+ ('45a15339-dd80-4ef7-ab8a-63c5d844e692', 'Escuela de Ciencias de la Educación', 'EDU', '#FB7185', 'GraduationCap', '2026-01-30 20:38:02.844498+00', '2026-01-30 20:38:02.844498+00');
--
@@ -1933,7 +1943,97 @@ INSERT INTO "public"."facultades" ("id", "nombre", "nombre_corto", "color", "ico
INSERT INTO "public"."carreras" ("id", "facultad_id", "nombre", "nombre_corto", "clave_sep", "activa", "creado_en", "actualizado_en") VALUES
('8208da08-d549-4359-8865-9d806bc54f19', '155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'Ingeniería en Cibernética y Sistemas Computacionales', 'CIB', 'CIB-ULSA', true, '2026-01-09 13:45:37.784586+00', '2026-01-09 21:34:25.770587+00'),
- ('089cdeda-d557-4a57-b49f-eb44921dfa3a', '155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'Ingeniería Mecatrónica', 'MTR', 'MTR_ULSA', true, '2026-01-09 21:36:01.354741+00', '2026-01-09 21:36:01.354741+00');
+ ('089cdeda-d557-4a57-b49f-eb44921dfa3a', '155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'Ingeniería Mecatrónica', 'MTR', 'MTR_ULSA', true, '2026-01-09 21:36:01.354741+00', '2026-01-09 21:36:01.354741+00'),
+ ('b56eb209-245d-4258-a6a7-9dcb1d231cb4', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Ingeniería Económica y Financiera', 'ING_FIN', NULL, true, '2026-01-30 14:23:23.012049+00', '2026-01-30 14:23:23.012049+00'),
+ ('33c850f2-160c-472c-bf14-b39031e8e47e', '21561e2c-22be-40ec-b0ad-520b5253f846', 'Área Curricular Común 2013', 'ACC', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('d65ca933-c6b4-4eb0-98fb-4a61acc49dec', '21561e2c-22be-40ec-b0ad-520b5253f846', 'Área Curricular Común 2021', 'ACC', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('66c6dbda-c2a4-4891-bd80-b09d01cf7532', 'cd9409f5-bbcd-466d-82eb-b206cea51b8b', 'Área Curricular Común (Centro de Idiomas) 2013', 'ACCI', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('def2d29e-dec6-42c5-b36a-5ea58873b661', 'cd9409f5-bbcd-466d-82eb-b206cea51b8b', 'Área Curricular Común (Centro de Idiomas) 2021', 'ACCI', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('21ce004a-2c89-422b-ae7d-675ffcb17365', 'ab73dbce-e750-4070-82b1-4e71347cd273', 'Licenciatura en Enfermería', 'LENF', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('196f80c0-0194-4eb2-b7c2-bd5fb70fb719', 'ab73dbce-e750-4070-82b1-4e71347cd273', 'Licenciatura en Fisioterapia y Promoción para la Salud', 'LFPS', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('4cd28b20-f419-410c-8224-7e24c060280a', 'ab73dbce-e750-4070-82b1-4e71347cd273', 'Licenciatura en Ciencias en el Deporte', 'LCD', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('6d3ea686-e38b-4baf-8fbe-5760bae33314', 'ab73dbce-e750-4070-82b1-4e71347cd273', 'Licenciatura en Medicina Dental', 'LMD', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('28627757-007a-4a57-b26d-1eb778b7d2fc', 'ab73dbce-e750-4070-82b1-4e71347cd273', 'Maestría en Nutrición Clínica', 'MNC', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('6f14401a-1080-4546-9a68-d9a13373bac0', 'ab73dbce-e750-4070-82b1-4e71347cd273', 'Licenciatura en Fisioterapia', 'LFIS', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('73f639f2-71a7-4313-ac21-23420eb6f738', '0d711469-4668-4910-b08e-88406ad30c9a', 'Licenciatura en Arquitectura', 'ARQ', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('cafa9b44-f894-48c8-8aa5-ac3dab3a384f', '0d711469-4668-4910-b08e-88406ad30c9a', 'Licenciatura en Ciencias de la Comunicación', 'LCC', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('4d52027e-ce61-4758-b643-448d09d6a1d9', '0d711469-4668-4910-b08e-88406ad30c9a', 'Licenciatura en Diseño Gráfico y Digital', 'LDGD', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('88ed9818-552e-4637-a8d6-d6f361451cac', '0d711469-4668-4910-b08e-88406ad30c9a', 'Licenciatura en Diseño de Ambientes Interiores y Exteriores', 'LDAI', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('b1ffa2d6-1237-4a8a-a01a-5dfbc70f3d90', '0d711469-4668-4910-b08e-88406ad30c9a', 'Especialidad en Gestión y Administración de Proyectos', 'EGAP', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('ad86cdab-cd81-457a-bbf5-afffec0a28cc', '0d711469-4668-4910-b08e-88406ad30c9a', 'Maestría en Estrategia e Innovación en Marcas', 'MEIM', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('624a2927-3f47-4b23-b80d-fe1a7f809b7f', '0d711469-4668-4910-b08e-88406ad30c9a', 'Maestría en Dirección Estratégica de Comunicación', 'MDEC', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('0f3366b5-9b05-4104-b87d-46931bd96f22', '0d711469-4668-4910-b08e-88406ad30c9a', 'Licenciatura en Comunicación', 'LCOM', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('0f3ed17e-38b7-4c4b-b1af-29096cb0215e', '0d711469-4668-4910-b08e-88406ad30c9a', 'Licenciatura en Diseño de Productos', 'LDP', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('be807290-075d-40a5-85bf-966ae111e743', '0d711469-4668-4910-b08e-88406ad30c9a', 'Especialidad en Dirección de Proyectos', 'EDP', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('20593041-d605-4174-8404-4b152041eece', '7a162523-1120-4fb9-b291-dfb606c8f1d4', 'Licenciatura en Derecho', 'LDER', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('431eaf1c-42a3-4a37-a7a2-79c350bbe1ae', '7a162523-1120-4fb9-b291-dfb606c8f1d4', 'Licenciatura en Relaciones Internacionales', 'LRI', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('170f14c6-1b9f-49a7-92f0-ccf79c41d750', '7a162523-1120-4fb9-b291-dfb606c8f1d4', 'Maestría en Derecho Financiero', 'MDF', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('7176dfbd-4b30-410e-b58c-51d03c261240', '7a162523-1120-4fb9-b291-dfb606c8f1d4', 'Especialidad en Derecho de Empresa', 'EDE', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('33e0ef2a-9144-4a00-b6a4-850f20a16843', '7a162523-1120-4fb9-b291-dfb606c8f1d4', 'Maestría en Gobernanza y Estrategia Internacional', 'MGEI', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('c4e4c745-5963-4bbd-84d8-7fbbf857c499', '7a162523-1120-4fb9-b291-dfb606c8f1d4', 'Doctorado en Ciencias Jurídicas', 'DCJ', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('43adfd53-06bc-422d-b5ac-0545aff30d65', '7a162523-1120-4fb9-b291-dfb606c8f1d4', 'Maestría en Derecho Civil', 'MDC', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('86a8d3ba-99fb-4e67-a03b-ce2c02d36db9', '7a162523-1120-4fb9-b291-dfb606c8f1d4', 'Maestría en Derecho de Empresa', 'MDE', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('0cab0fb0-7399-4696-a89b-f4d41fba8ae6', '7a162523-1120-4fb9-b291-dfb606c8f1d4', 'Maestría en Justicia Penal', 'MJP', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('5c1b2052-6045-444f-a333-4f4234edb03f', 'a977a640-709d-47d7-a306-9acbe4a867a9', 'Licenciatura en Educación Preescolar', 'LEP', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('2937ed73-e56d-4995-91a3-198215b741bc', 'a977a640-709d-47d7-a306-9acbe4a867a9', 'Licenciatura en Ciencias Religiosas', 'LCR', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('55dd719d-1545-421f-93c6-adc082c87e50', 'a977a640-709d-47d7-a306-9acbe4a867a9', 'Licenciatura en Filosofía', 'LFIL', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('82408bb8-eb5b-4a16-97eb-be20494a88c8', 'a977a640-709d-47d7-a306-9acbe4a867a9', 'Maestría en Filosofía Social', 'MFS', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('d644f8df-9329-4afa-a0e3-0d4572bbed5e', 'a977a640-709d-47d7-a306-9acbe4a867a9', 'Área de Gestión Educativa', 'AGE', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('649f86d4-b41e-4255-9519-086f603920b4', 'a977a640-709d-47d7-a306-9acbe4a867a9', 'Área de Intervención Docente', 'AID', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('747efb86-3955-4176-ba3c-9a906bbecde2', 'a977a640-709d-47d7-a306-9acbe4a867a9', 'Especialidad en Gestión de los Aprendizajes', 'EGA', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('8f98cef0-a585-4da2-bcda-4f871ccf80c4', 'a977a640-709d-47d7-a306-9acbe4a867a9', 'Doctorado en Educación', 'DOCE', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('c8a8cd55-e124-4393-9775-b2da161297dd', 'a977a640-709d-47d7-a306-9acbe4a867a9', 'Licenciatura en Psicología', 'LPS', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('6d086d1b-aaea-4b76-9b16-fa8f3e29d9fa', 'a977a640-709d-47d7-a306-9acbe4a867a9', 'Licenciatura en Educación Primaria', 'LEPR', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('e7b89d58-6b55-4993-a96f-bb4eb8c2eb88', 'a977a640-709d-47d7-a306-9acbe4a867a9', 'Licenciatura en Ciencias de la Educación', 'LCE', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('b4e0ad06-84b0-40fc-8228-a75b0c9cbd9b', 'a977a640-709d-47d7-a306-9acbe4a867a9', 'Licenciatura en Pedagogía', 'LPED', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('41298122-2ffb-49cb-91c8-2decce409d81', 'a977a640-709d-47d7-a306-9acbe4a867a9', 'Licenciatura en Teología', 'LTEO', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('ade7dcff-d65b-4d95-919b-d2e09788253f', '7884f606-71b0-4f67-92da-bf22e0601480', 'Licenciatura en Médico Cirujano', 'LMC', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('ab3bb83e-fcd9-4838-92f1-b6c4fb98ed2e', 'd17b19c6-b7ab-4bdc-8bb9-ddf6f9e0358e', 'Licenciatura en Ingeniería Química', 'LIQ', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('991edcde-b77e-4d22-b76b-3bf6ec3d3555', 'd17b19c6-b7ab-4bdc-8bb9-ddf6f9e0358e', 'Licenciatura en Químico Farmacéutico Biólogo', 'QFB', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('2017ce3d-89ea-48c0-b36f-fe05f1e364cc', 'd17b19c6-b7ab-4bdc-8bb9-ddf6f9e0358e', 'Licenciatura en Química de Alimentos', 'LQA', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('7bb3c955-fee8-42af-bc5b-6f96fdbcedcd', 'd17b19c6-b7ab-4bdc-8bb9-ddf6f9e0358e', 'Licenciatura en Ingeniería Ambiental', 'LIA', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('eee00014-3877-4572-a8af-cdd36ca04802', 'd17b19c6-b7ab-4bdc-8bb9-ddf6f9e0358e', 'Maestría en Ciencia de los Alimentos y Nutrición Humana', 'MCAN', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('2e4ac125-101e-4897-80a2-e4e9a313d43f', 'd17b19c6-b7ab-4bdc-8bb9-ddf6f9e0358e', 'Maestría en Farmacología Clínica', 'MFC', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('8b25bb3f-dbb9-46be-8423-761964bd65f9', 'd17b19c6-b7ab-4bdc-8bb9-ddf6f9e0358e', 'Maestría en Calidad y Estadística Aplicada', 'MCEA', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('b425282f-9e08-47da-b755-ede93d198eed', 'd17b19c6-b7ab-4bdc-8bb9-ddf6f9e0358e', 'Maestría en Ingeniería de Proyectos', 'MIP', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('334763cf-973e-4610-9ee3-6475e861b783', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Licenciatura en Contaduría y Finanzas', 'LCF', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('7075d507-6404-4b18-a5cb-e07a187bae55', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Licenciatura en Mercadotecnia', 'LMER', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('90cad868-c0db-4b8b-a346-2272655141c8', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Licenciatura en Comercio y Negocios Internacionales', 'LCNI', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('98ac0686-c6cf-4b89-bb7c-616c7d23dc38', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Licenciatura en Administración', 'LADM', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('c296f7b6-351b-44dc-a08c-2a676aa6786c', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Licenciatura en Tecnologías de Información', 'LTI', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('d2c59b24-0483-49d0-b9c3-cff6b7af5847', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Maestría en Dirección de Organizaciones de la Salud', 'MDOS', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('cc757121-41aa-4532-9050-81e8adc7fbb8', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Licenciatura en Actuaría', 'LACT', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('c4f371bb-9132-4dbc-b4c3-def5d6abe627', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Especialidad en Mercadotecnia y Publicidad', 'EMP', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('545a7ee2-506c-4a15-970f-8bd9c9267b9e', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Especialidad en Finanzas Corporativas y Bursátiles', 'EFCB', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('c33b8931-6648-4916-96bd-177cdaf75002', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Especialidad en Logística y Cadena de Suministro', 'ELCS', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('6d78b05e-9838-4256-90d3-28d270439e81', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Especialidad en Administración de Organizaciones de la Salud', 'EAOS', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('ece2d1f2-4f70-47fe-92aa-7b56dadf78ca', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Maestría en Ciencias Actuariales', 'MCA', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('cf97a7fc-e769-4597-b470-72fad0d37d26', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Doctorado en Administración', 'DADM', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('18ce85d7-4bea-4909-bad4-e0b30aa8f0d8', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Maestría en Administración de Organizaciones de la Salud', 'MAOS', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('6e3056f7-1147-41df-8f9b-d39e72c20ea5', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Maestría en Administración de Negocios Internacionales', 'MANI', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('1e8360a6-a86a-4a66-9b9e-68cbde3b3b1e', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Maestría en Tecnologías de Información en la Dirección de Negocios', 'MTID', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('324f1f4a-8bcc-4b60-b1f5-86364ee00cbb', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Maestría en Administración', 'MADM', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('b0c5b5fe-ef94-4a34-a29a-80e3b9d28552', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Maestría en Ingeniería Económica y Financiera', 'MIEF', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('2769395d-08bc-4903-89a6-04530dc7e1c7', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Maestría en Administración de Negocios Internacionales (MIEX)', 'MIEX', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('5f3c379b-a064-4658-96de-f4301b8f9cef', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Maestría en Gestión Estratégica del Capital Humano', 'MGEC', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('ddb222ae-2bd1-4e0b-a389-deb6f24e74fd', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Licenciatura en Administración e Innovación de Negocios', 'LAIN', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('e733243f-9791-48f0-8dd8-bd54e119a7f0', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Licenciatura en Ingeniería Económica y Financiera', 'LIEF', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('b8e1d826-8ea8-4b64-8c57-a544d82e52b6', '45a6da79-1e2d-4854-9953-6229f46c8e82', 'Licenciatura en Transformación Digital para los Negocios', 'LTDN', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('00f6f9a0-56bc-43e6-a0e7-5ac2c00fbc2a', '155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'Licenciatura en Ingeniería Electrónica', 'LIE', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('6dcde533-b35b-4c53-b803-e2ccc275a04f', '155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'Licenciatura en Ingeniería Civil', 'LIC', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('48220a64-7bd5-4dea-bb95-31f1af9dbcbd', '155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'Licenciatura en Ingeniería Mecánica y en Sistemas Energéticos', 'LIME', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('a607a966-d454-4f08-950e-d8e6ec1e1545', '155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'Licenciatura en Ingeniería Electrónica para Sistemas Inteligentes', 'LIES', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('a75b6da6-ad46-43d8-a069-31a203a9d340', '155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'Licenciatura en Ingeniería Mecatrónica', 'LIM', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('c0fb8394-656f-4709-a92e-351daf477c60', '155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'Licenciatura en Ingeniería Industrial', 'LII', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('8f1ce751-949b-45dd-8e38-e10c64b077fd', '155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'Licenciatura en Ingeniería Cibernética y Sistemas Computacionales', 'LICS', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('76224caa-1203-4b18-b9df-758f1f9a97fb', '155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'Licenciatura en Ingeniería Biomédica', 'LIB', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('01552f83-5625-4532-a8ae-723cc864494c', '155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'Maestría en Gerencia de Proyectos Inmobiliarios', 'MGPI', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('81278d11-5487-41b3-b8bd-708cdd147ed4', '155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'Maestría en Dirección Industrial', 'MDI', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('d8089840-c7ba-4e64-b073-d8d93092424f', '155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'Especialidad en Dirección Industrial', 'EDI', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('fbae0d9a-282c-4eb0-bc48-02bb11015d57', '155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'Maestría en Gestión de Proyectos y de Empresas Constructoras', 'MGPC', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('c8f8c573-ec33-446d-886d-338def5d55af', '155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'Especialidad en Energías Renovables', 'EER', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('83fcc355-b79b-4650-a757-0c935127e161', '155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'Maestría en Ciberseguridad', 'MCIB', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00'),
+ ('6a9ae3f4-91ae-4e56-8806-9d9f4692d796', '155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'Especialidad en Inteligencia de Datos', 'EID', NULL, true, '2026-01-30 20:55:12.677212+00', '2026-01-30 20:55:12.677212+00');
--
@@ -1951,7 +2051,7 @@ INSERT INTO "public"."estados_plan" ("id", "clave", "etiqueta", "orden", "es_fin
--
INSERT INTO "public"."estructuras_asignatura" ("id", "nombre", "version", "definicion", "creado_en", "actualizado_en") VALUES
- ('7856e9cc-9ac1-4a31-b93a-0624d4c5e1de', 'Asignatura SEP/ULSA (semilla)', '1.0', '{"type": "object", "required": ["descripcion", "nombre", "fines_de_aprendizaje_o_formacion", "perfil_de_egreso"], "properties": {"nombre": {"type": "string", "description": "Indicar la denominación (nombre) del plan de estudios. Corresponde al requisito de denominación propuesto en el Artículo 8, fracción IV del Acuerdo 17/11/17."}, "descripcion": {"type": "string", "description": "Una breve descripción"}, "perfil_de_egreso": {"type": "string", "description": "Describir los atributos que habrán desarrollado los estudiantes al concluir el plan de estudios, acordes al nivel educativo y denominación propuesta. De preferencia, dividirlo en conocimientos, habilidades y actitudes. Corresponde al requisito ''Perfil de egreso'' del Artículo 8, fracción VI del Acuerdo 17/11/17."}, "fines_de_aprendizaje_o_formacion": {"type": "string", "description": "Describir los aprendizajes que lograrán los estudiantes al concluir el plan de estudios. Deberá corresponder con el nivel educativo y la denominación del plan de estudios. Corresponde al requisito ''Descripción de los fines del aprendizaje o formación'' del Artículo 8, fracción IV del Acuerdo 17/11/17."}}, "additionalProperties": false}', '2026-01-09 13:45:37.784586+00', '2026-01-13 20:41:14.261408+00');
+ ('7856e9cc-9ac1-4a31-b93a-0624d4c5e1de', 'Asignatura SEP/ULSA (semilla)', '1.0', '{"type": "object", "required": ["denominacion_de_la_asignatura_o_unidad_de_aprendizaje", "ciclo", "clave_de_la_asignatura", "fines_de_aprendizaje_o_formacion", "contenido_tematico", "actividades_de_aprendizaje_bajo_conduccion_de_un_academico", "actividades_de_aprendizaje_independientes", "criterios_de_evaluacion", "modalidades_tecnologicas_e_informaticas"], "properties": {"ciclo": {"type": "string", "title": "Ciclo", "examples": ["Primer semestre"], "description": "Indicar el ciclo en el que se impartirá la asignatura en el orden establecido en el mapa curricular. En caso de ser flexible; indicar el área o módulo al que pertenece. Para currículum rígido que contemple asignaturas optativas; indicar “Optativa”, y en caso de que se curse en un ciclo determinado, señalar el ciclo acompañado de la leyenda “Optativa”."}, "contenido_tematico": {"type": "string", "title": "Contenido temático", "description": "Enunciar, en idioma español, los temas y subtemas que se abordarán en la asignatura o unidad de aprendizaje; estructurados de manera lógica y secuencial y numerados en orden jerárquico. Éstos deberán ser acordes con la carga horaria asignada, sustentar la denominación de la asignatura y contribuir al logro de los fines de aprendizaje."}, "clave_de_la_asignatura": {"type": "string", "title": "Clave de la asignatura", "examples": ["MAT-101"], "description": "Anotar la clave que identifica a la asignatura o unidad de aprendizaje; la cual debe coincidir con la señalada en el mapa curricular."}, "criterios_de_evaluacion": {"type": "string", "title": "Criterios de evaluación", "description": "Enlistar las actividades o productos que considerarán para valorar los aprendizajes logrados por los estudiantes, señalar el porcentaje para cada uno, de tal manera que sumen el 100%. Éstos deberán ser acordes con las actividades de aprendizaje."}, "fines_de_aprendizaje_o_formacion": {"type": "string", "title": "Fines de aprendizaje o formación", "description": "Describir los aprendizajes y fines que alcanzará el estudiante al concluir la asignatura. Deberá sustentar la denominación de la asignatura, vincularse con los contenidos temáticos y coadyuvar al logro del fin de aprendizaje y perfil de egreso del plan de estudios."}, "modalidades_tecnologicas_e_informaticas": {"type": "string", "title": "Modalidades tecnológicas e informáticas", "description": "Describir los recursos tecnológicos y de comunicación que se emplearán para el desarrollo del proceso de enseñanza-aprendizaje de la asignatura. Para las modalidades no escolarizada y mixta, este elemento es obligatorio."}, "actividades_de_aprendizaje_independientes": {"type": "string", "title": "Actividades de aprendizaje independientes", "description": "Señalar las actividades de aprendizaje que desarrollará el estudiante de manera independiente. Éstas deberán ser acordes con la naturaleza de la asignatura y la modalidad educativa."}, "denominacion_de_la_asignatura_o_unidad_de_aprendizaje": {"type": "string", "title": "Denominación de la asignatura o unidad de aprendizaje", "examples": ["Matemáticas I"], "description": "Señalar el nombre de la asignatura o unidad de aprendizaje de igual manera que en el mapa curricular."}, "actividades_de_aprendizaje_bajo_conduccion_de_un_academico": {"type": "string", "title": "Actividades de aprendizaje bajo conducción de un académico", "description": "Señalar las actividades de aprendizaje que desarrollará el estudiante en las horas-clase. Éstas deberán ser acordes con la naturaleza de la asignatura, modalidad educativa e instalaciones empleadas, además de contribuir al logro de los fines de aprendizaje."}}, "additionalProperties": false}', '2026-01-09 13:45:37.784586+00', '2026-01-28 15:42:54.449734+00');
--
@@ -1959,16 +2059,21 @@ INSERT INTO "public"."estructuras_asignatura" ("id", "nombre", "version", "defin
--
INSERT INTO "public"."estructuras_plan" ("id", "nombre", "tipo", "template_id", "definicion", "creado_en", "actualizado_en") VALUES
- ('69fb2b77-5a95-47e0-bf1f-389d384200e4', 'Plan base SEP/ULSA (2026)', 'CURRICULAR', '1337636802549652331', '{"type": "object", "required": ["nivel", "nombre", "modalidad_educativa", "antecedente_academico", "area_de_estudio", "clave_del_plan_de_estudios", "diseno_curricular", "total_de_ciclos_del_plan_de_estudios", "duracion_del_ciclo_escolar", "carga_horaria_a_la_semana", "fines_de_aprendizaje_o_formacion", "perfil_de_egreso", "programa_de_investigacion", "curso_propedeutico", "perfil_de_ingreso", "administracion_y_operatividad_del_plan_de_estudios", "sustento_teorico_del_modelo_curricular", "justificacion_de_la_propuesta_curricular", "propuesta_de_evaluacion_periodica_del_plan_de_estudios"], "properties": {"nivel": {"type": "string", "description": "Indicar el nivel educativo del plan de estudios. Corresponde al requisito de ''Nivel educativo'' propuesto en el Artículo 8, fracción I del Acuerdo 17/11/17."}, "nombre": {"type": "string", "description": "Indicar la denominación (nombre) del plan de estudios. Corresponde al requisito de denominación propuesto en el Artículo 8, fracción IV del Acuerdo 17/11/17."}, "area_de_estudio": {"type": "string", "description": "Señalar el “campo amplio” al que pertenece el plan de estudios. Para ello, considerar la Clasificación Mexicana de Planes de Estudio por Campos de Formación Académica (CMPE 2016), publicado por INEGI. Deberá vincularse con el fin de aprendizaje y perfil de egreso del plan de estudios. Dato de clasificación del Anexo 1."}, "perfil_de_egreso": {"type": "string", "description": "Describir los atributos que habrán desarrollado los estudiantes al concluir el plan de estudios, acordes al nivel educativo y denominación propuesta. De preferencia, dividirlo en conocimientos, habilidades y actitudes. Corresponde al requisito ''Perfil de egreso'' del Artículo 8, fracción VI del Acuerdo 17/11/17."}, "diseno_curricular": {"enum": ["Rígido", "Flexible"], "type": "string", "description": "Indicar si es “Rígido” o “Flexible”. Define la estructura para el requisito ''Mapa curricular'' del Artículo 8, fracción VII del Acuerdo 17/11/17."}, "perfil_de_ingreso": {"type": "string", "description": "Indicar las condiciones académicas ideales (conocimientos, habilidades y aptitudes), para incorporarse al plan de estudios. Deberá vincularse con el nivel educativo y campo de estudio. Corresponde al requisito ''Perfil de ingreso'' (condiciones mínimas requeridas) del Artículo 8, fracción V del Acuerdo 17/11/17."}, "curso_propedeutico": {"type": ["string", "null"], "description": "Señalar las situaciones en las que aplicará el curso propedéutico, la población a la que estará dirigido, su duración, la relación con el plan de estudios, así como la información que considere relevante. Elemento opcional mencionado en las guías de llenado del Anexo 1."}, "modalidad_educativa": {"enum": ["Escolar", "No escolarizada", "Mixta"], "type": "string", "description": "Señalar la modalidad educativa en la que se impartirá el plan de estudios: escolar, no escolarizada o mixta. Corresponde al requisito de ''Modalidad educativa'' del Artículo 8, fracción II del Acuerdo 17/11/17."}, "antecedente_academico": {"type": "string", "description": "Señalar el nivel educativo requerido para ingresar al plan de estudios. Para posgrados, además del nivel educativo; indicar la(s) disciplina(s) o área(s) afín(es) al plan de estudios. Corresponde al requisito ''Perfil de ingreso'' (antecedente académico necesario) del Artículo 8, fracción V del Acuerdo 17/11/17."}, "carga_horaria_a_la_semana": {"type": "number", "description": "Indicar la máxima carga horaria impartida a la semana; no mayor a 50 horas bajo la conducción de un académico. Corresponde a la restricción ''sin exceder una carga máxima de 50 horas efectivas por semana'' del Artículo 8, fracción III del Acuerdo 17/11/17."}, "programa_de_investigacion": {"type": ["string", "null"], "description": "(Obligatorio para el nivel de Doctorado y para aquéllas Maestrías orientadas a la investigación). Presentar el programa de investigación que describa los objetivos del programa y líneas de investigación con sus respectivos objetivos. Asimismo, detallar la vinculación con las asignaturas que conforman el plan de estudios, la metodología y presentación de las investigaciones, así como el perfil de los docentes que guiarán su desarrollo. Requisito obligatorio solo para Doctorados o Maestrías con orientación a investigación según el Artículo 10, fracción III incisos b) y c) del Acuerdo 17/11/17."}, "clave_del_plan_de_estudios": {"type": "string", "description": "Señalar el año en el que solicita el trámite. Dato administrativo del Anexo 1."}, "duracion_del_ciclo_escolar": {"type": "string", "description": "Señalar el número mínimo de semanas efectivas de clase que integran el tipo de ciclo en el que se imparte el plan de estudios. Corresponde exactamente al requisito ''Duración mínima en semanas'' del Artículo 8, fracción III del Acuerdo 17/11/17."}, "fines_de_aprendizaje_o_formacion": {"type": "string", "description": "Describir los aprendizajes que lograrán los estudiantes al concluir el plan de estudios. Deberá corresponder con el nivel educativo y la denominación del plan de estudios. Corresponde al requisito ''Descripción de los fines del aprendizaje o formación'' del Artículo 8, fracción IV del Acuerdo 17/11/17."}, "total_de_ciclos_del_plan_de_estudios": {"type": "string", "description": "Establecer el número de ciclos escolares que integran el plan de estudios. Corresponde a ''señalando el número de ciclos en que se impartirá'' del Artículo 8, fracción III del Acuerdo 17/11/17."}, "sustento_teorico_del_modelo_curricular": {"type": ["string", "null"], "description": "Presentar de forma concreta el marco conceptual en el que se justifica y fundamenta el diseño y organización curricular del plan de estudios. Corresponde al ''sustento teórico del modelo curricular'' que el particular puede incluir según el Artículo 8 (párrafo tercero) del Acuerdo 17/11/17."}, "justificacion_de_la_propuesta_curricular": {"type": ["string", "null"], "description": "(Exclusivo para modalidades no escolarizada y mixta). Describir cómo se impartirá el plan de estudios en la modalidad educativa elegida, los recursos tecnológicos y/o didácticos que emplearán para su desarrollo y su vinculación con las asignaturas y actividades de aprendizaje. Corresponde a lo solicitado en el Artículo 14 (justificación de modalidad) y Artículo 15 (especificaciones de plataforma/modelo) del Acuerdo 17/11/17."}, "administracion_y_operatividad_del_plan_de_estudios": {"type": ["string", "null"], "description": "Describir la administración del plan de estudios, así como su operatividad; considerando el tipo de diseño curricular empleado. Si es flexible; incorporar el número mínimo y máximo de asignaturas que se cursarán por ciclo. Corresponde a la ''descripción detallada de la(s) forma(s) de administración y operatividad'' permitida en el Artículo 8 (párrafo tercero) del Acuerdo 17/11/17."}, "propuesta_de_evaluacion_periodica_del_plan_de_estudios": {"type": ["string", "null"], "description": "Describir de manera detallada la metodología que emplearán para la evaluación del plan de estudios, incluyendo los instrumentos y periodicidad en la que se llevará a cabo; con la finalidad de mantenerlo actualizado y acorde a las necesidades educativas y disciplinares. Corresponde al requisito ''Propuesta de evaluación periódica del Plan de estudio'' del Artículo 8, fracción VIII del Acuerdo 17/11/17."}}, "additionalProperties": false}', '2026-01-09 13:45:37.784586+00', '2026-01-20 14:06:17.099626+00');
+ ('69fb2b77-5a95-47e0-bf1f-389d384200e4', 'Plan base SEP/ULSA (2026)', 'CURRICULAR', '1337636802549652331', '{"type": "object", "required": ["nombre_autorizado_de_la_institucion", "nivel_y_nombre_del_plan_de_estudios", "modalidad_educativa", "vigencia", "antecedente_academico", "area_de_estudio", "clave_del_plan_de_estudios", "diseno_curricular", "total_de_ciclos_del_plan_de_estudios", "duracion_del_ciclo_escolar", "carga_horaria_a_la_semana", "fines_de_aprendizaje_o_formacion", "perfil_de_egreso", "programa_de_investigacion", "curso_propedeutico", "perfil_de_ingreso", "administracion_y_operatividad_del_plan_de_estudios", "sustento_teorico_del_modelo_curricular", "justificacion_de_la_propuesta_curricular", "propuesta_de_evaluacion_periodica_del_plan_de_estudios", "nombre_y_cargo_de_la_persona_facultada_para_autorizar_el_plan_de_estudios"], "properties": {"vigencia": {"type": "string", "title": "Vigencia", "examples": ["2024-2029"], "description": "(Espacio para uso exclusivo de la autoridad educativa)", "referencia_normativa": "Dato administrativo del Anexo 1."}, "area_de_estudio": {"type": "string", "title": "Área de estudio", "examples": ["Ingeniería, industria y construcción", "Ciencias sociales y derecho"], "description": "Señalar el “campo amplio” al que pertenece el plan de estudios. Para ello, considerar la Clasificación Mexicana de Planes de Estudio por Campos de Formación Académica (CMPE 2016), publicado por INEGI.", "referencia_normativa": "Deberá vincularse con el fin de aprendizaje y perfil de egreso del plan de estudios."}, "perfil_de_egreso": {"type": "string", "title": "Perfil de egreso", "examples": ["El egresado será capaz de diseñar estrategias legales..."], "description": "Describir los atributos que habrán desarrollado los estudiantes al concluir el plan de estudios, acordes al nivel educativo y denominación propuesta. De preferencia, dividirlo en conocimientos, habilidades y actitudes.", "referencia_normativa": "Corresponde al requisito ''Perfil de egreso'' del Artículo 8, fracción VI del Acuerdo 17/11/17."}, "diseno_curricular": {"enum": ["Rígido", "Flexible"], "type": "string", "title": "Diseño curricular", "examples": ["Flexible"], "description": "Indicar si es “Rígido” o “Flexible”.", "referencia_normativa": "Define la estructura para el requisito ''Mapa curricular'' del Artículo 8, fracción VII del Acuerdo 17/11/17."}, "perfil_de_ingreso": {"type": "string", "title": "Perfil de ingreso", "examples": ["Conocimientos básicos de matemáticas, capacidad de análisis y redacción."], "description": "Indicar las condiciones académicas ideales (conocimientos, habilidades y aptitudes), para incorporarse al plan de estudios. Deberá vincularse con el nivel educativo y campo de estudio.", "referencia_normativa": "Corresponde al requisito ''Perfil de ingreso'' (condiciones mínimas requeridas) del Artículo 8, fracción V del Acuerdo 17/11/17."}, "curso_propedeutico": {"type": ["string", "null"], "title": "Curso propedéutico", "examples": ["Curso de nivelación de 40 horas obligatorio para aspirantes de áreas ajenas."], "description": "Señalar las situaciones en las que aplicará el curso propedéutico, la población a la que estará dirigido, su duración, la relación con el plan de estudios, así como la información que considere relevante.", "referencia_normativa": "Elemento opcional mencionado en las guías de llenado del Anexo 1."}, "modalidad_educativa": {"enum": ["Escolar", "No escolarizada", "Mixta"], "type": "string", "title": "Modalidad educativa", "examples": ["Escolar"], "description": "Señalar la modalidad educativa en la que se impartirá el plan de estudios: escolar, no escolarizada o mixta.", "referencia_normativa": "Corresponde al requisito de ''Modalidad educativa'' del Artículo 8, fracción II del Acuerdo 17/11/17."}, "antecedente_academico": {"type": "string", "title": "Antecedente académico", "examples": ["Bachillerato concluido o equivalente.", "Título de Licenciatura en áreas afines a la salud."], "description": "Señalar el nivel educativo requerido para ingresar al plan de estudios. Para posgrados, además del nivel educativo; indicar la(s) disciplina(s) o área(s) afín(es) al plan de estudios.", "referencia_normativa": "Corresponde al requisito ''Perfil de ingreso'' (antecedente académico necesario) del Artículo 8, fracción V del Acuerdo 17/11/17."}, "carga_horaria_a_la_semana": {"type": "number", "title": "Carga horaria a la semana", "examples": [25], "description": "Indicar la máxima carga horaria impartida a la semana; no mayor a 50 horas bajo la conducción de un académico.", "referencia_normativa": "Corresponde a la restricción ''sin exceder una carga máxima de 50 horas efectivas por semana'' del Artículo 8, fracción III del Acuerdo 17/11/17."}, "programa_de_investigacion": {"type": ["string", "null"], "title": "Programa de investigación", "examples": ["Línea 1: Innovación Educativa. Objetivo: Analizar el impacto de..."], "description": "(Obligatorio para el nivel de Doctorado y para aquéllas Maestrías orientadas a la investigación). Presentar el programa de investigación que describa los objetivos del programa y líneas de investigación con sus respectivos objetivos. Asimismo, detallar la vinculación con las asignaturas que conforman el plan de estudios, la metodología y presentación de las investigaciones, así como el perfil de los docentes que guiarán su desarrollo.", "referencia_normativa": "Requisito obligatorio solo para Doctorados o Maestrías con orientación a investigación según el Artículo 10, fracción III incisos b) y c) del Acuerdo 17/11/17."}, "clave_del_plan_de_estudios": {"type": "string", "title": "Clave / Año del plan", "examples": ["2024"], "description": "Señalar el año en el que solicita el trámite.", "referencia_normativa": "Dato administrativo del Anexo 1."}, "duracion_del_ciclo_escolar": {"type": "string", "title": "Duración mínima en semanas de cada ciclo escolar", "examples": ["16 semanas"], "description": "Señalar el número mínimo de semanas efectivas de clase que integran el tipo de ciclo en el que se imparte el plan de estudios.", "referencia_normativa": "Corresponde exactamente al requisito ''Duración mínima en semanas'' del Artículo 8, fracción III del Acuerdo 17/11/17."}, "fines_de_aprendizaje_o_formacion": {"type": "string", "title": "Fin de aprendizaje o formación", "examples": ["El estudiante analizará los principios fundamentales del derecho penal..."], "description": "Describir los aprendizajes que lograrán los estudiantes al concluir el plan de estudios. Deberá corresponder con el nivel educativo y la denominación del plan de estudios.", "referencia_normativa": "Corresponde al requisito ''Descripción de los fines del aprendizaje o formación'' del Artículo 8, fracción IV del Acuerdo 17/11/17."}, "nivel_y_nombre_del_plan_de_estudios": {"type": "string", "title": "Nivel y nombre del plan de estudios", "examples": ["Licenciatura en Derecho", "Maestría en Ciencias de la Computación"], "description": "Indicar el nivel educativo y denominación del plan de estudios.", "referencia_normativa": "Corresponde al requisito ''Nivel y denominación del plan de estudios'' del Artículo 8, fracción I del Acuerdo 17/11/17."}, "nombre_autorizado_de_la_institucion": {"type": "string", "title": "Nombre autorizado de la institución", "examples": ["Universidad La Salle A.C."], "description": "Proporcionar el nombre de la institución autorizado por SEP.", "referencia_normativa": "Dato administrativo del Anexo 1."}, "total_de_ciclos_del_plan_de_estudios": {"type": "string", "title": "Número de ciclos escolares que integrarán el plan de estudio", "examples": ["9 cuatrimestres", "8 semestres"], "description": "En diseño flexible especificar mínimo y máximo de ciclos. En diseño rígido especificar el total de ciclos.", "referencia_normativa": "Corresponde a ''señalando el número de ciclos en que se impartirá'' del Artículo 8, fracción III del Acuerdo 17/11/17."}, "sustento_teorico_del_modelo_curricular": {"type": ["string", "null"], "title": "Sustento teórico del modelo curricular", "examples": ["Modelo por competencias profesionales fundamentado en el constructivismo..."], "description": "Presentar de forma concreta el marco conceptual en el que se justifica y fundamenta el diseño y organización curricular del plan de estudios.", "referencia_normativa": "Corresponde al ''sustento teórico del modelo curricular'' que el particular puede incluir según el Artículo 8 (párrafo tercero) del Acuerdo 17/11/17."}, "justificacion_de_la_propuesta_curricular": {"type": ["string", "null"], "title": "Justificación de la propuesta curricular en la modalidad no escolarizada o mixta", "examples": ["Se utilizará una plataforma LMS asincrónica complementada con sesiones síncronas..."], "description": "(Exclusivo para modalidades no escolarizada y mixta). Describir cómo se impartirá el plan de estudios en la modalidad educativa elegida, los recursos tecnológicos y/o didácticos que emplearán para su desarrollo y su vinculación con las asignaturas y actividades de aprendizaje.", "referencia_normativa": "Corresponde a lo solicitado en el Artículo 14 (justificación de modalidad) y Artículo 15 (especificaciones de plataforma/modelo) del Acuerdo 17/11/17."}, "administracion_y_operatividad_del_plan_de_estudios": {"type": ["string", "null"], "title": "Administración y operatividad del plan de estudios", "examples": ["El estudiante podrá inscribir un mínimo de 3 y un máximo de 6 asignaturas por ciclo..."], "description": "Describir la administración del plan de estudios, así como su operatividad; considerando el tipo de diseño curricular empleado. Si es flexible; incorporar el número mínimo y máximo de asignaturas que se cursarán por ciclo.", "referencia_normativa": "Corresponde a la ''descripción detallada de la(s) forma(s) de administración y operatividad'' permitida en el Artículo 8 (párrafo tercero) del Acuerdo 17/11/17."}, "propuesta_de_evaluacion_periodica_del_plan_de_estudios": {"type": ["string", "null"], "title": "Propuesta de evaluación periódica del plan de estudios", "examples": ["Revisión quinquenal mediante encuestas a empleadores, egresados y análisis de tendencias del mercado."], "description": "Describir de manera detallada la metodología que emplearán para la evaluación del plan de estudios, incluyendo los instrumentos y periodicidad en la que se llevará a cabo; con la finalidad de mantenerlo actualizado y acorde a las necesidades educativas y disciplinares.", "referencia_normativa": "Corresponde al requisito ''Propuesta de evaluación periódica del Plan de estudio'' del Artículo 8, fracción VIII del Acuerdo 17/11/17."}, "nombre_y_cargo_de_la_persona_facultada_para_autorizar_el_plan_de_estudios": {"type": "string", "title": "Nombre y cargo de la persona facultada para autorizar el plan de estudios", "examples": ["Dr. Juan Pérez Gómez, Rector de la Universidad XYZ"], "description": "Indicar el nombre y cargo de la persona facultada para autorizar el plan de estudios.", "referencia_normativa": "Dato administrativo del Anexo 1."}}, "additionalProperties": false}', '2026-01-09 13:45:37.784586+00', '2026-01-28 23:09:36.914016+00');
--
-- Data for Name: planes_estudio; Type: TABLE DATA; Schema: public; Owner: postgres
--
-INSERT INTO "public"."planes_estudio" ("id", "carrera_id", "estructura_id", "nombre", "nivel", "tipo_ciclo", "numero_ciclos", "datos", "estado_actual_id", "activo", "tipo_origen", "meta_origen", "creado_por", "actualizado_por", "creado_en", "actualizado_en") VALUES
- ('c90aebbb-454e-4a8e-b2a3-99dd1c200e86', '8208da08-d549-4359-8865-9d806bc54f19', '69fb2b77-5a95-47e0-bf1f-389d384200e4', 'Ingeniería en Cibernética y Ciberseguridad (2026)', 'Licenciatura', 'Semestre', 9, '{"nivel": "Licenciatura", "nombre": "Ingeniería Cibernética y Ciberseguridad", "area_de_estudio": "5. Ingeniería, manufactura y construcción (CMPE 2016)", "perfil_de_egreso": "Al finalizar, el egresado demostrará los siguientes atributos: Conocimientos Fundamentos de matemáticas, física y probabilidad aplicados a señales, control y comunicaciones. Arquitecturas de redes, sistemas operativos, virtualización, servicios en la nube e integración TI/OT. Modelado y control de sistemas ciberfísicos y embebidos, protocolos industriales e IoT. Criptografía aplicada, gestión de identidades, gobierno y gestión de seguridad de la información. Normativa y marcos de referencia: ISO/IEC 27001/27002, NIST CSF/800-53, MITRE ATT&CK, OWASP, ENS (referencial), legislación mexicana aplicable en protección de datos y delitos informáticos. Auditoría de seguridad, pruebas de penetración éticas, respuesta a incidentes y continuidad del negocio. Gestión de proyectos, economía de la ciberseguridad y ética profesional. HabilidadesDiseñar y desplegar arquitecturas seguras para redes, nube y sistemas ciberfísicos. Programar en C/C++, Python y scripts para automatización, pruebas y análisis. Configurar y operar firewalls, IDS/IPS, SIEM, WAF, VPN, segmentación y Zero Trust básico. Conducir análisis de riesgo, planes de tratamiento y auditorías técnicas y de cumplimiento. Orquestar procesos de detección, respuesta y recuperación ante incidentes. Desarrollar prototipos de IoT seguro y realizar hardening de dispositivos embebidos. Comunicar resultados técnicos mediante informes ejecutivos y presentaciones efectivas. Actitudes y valoresÉtica, responsabilidad social y legalidad en el manejo de información y en pruebas de seguridad. Aprendizaje autónomo y actualización permanente. Trabajo colaborativo, liderazgo y orientación a resultados. Respeto a la diversidad, perspectiva de género e inclusión.", "diseno_curricular": "Flexible", "perfil_de_ingreso": " ConocimientosMatemáticas de bachillerato: álgebra, funciones y trigonometría. Ofimática e informática básica; navegación segura en Internet. Inglés técnico básico (lectura de manuales). HabilidadesPensamiento lógico-matemático y resolución de problemas. Comunicación escrita y oral. Gestión del tiempo y aprendizaje autónomo. ActitudesInterés por la tecnología, la seguridad digital y la innovación. Ética, responsabilidad y respeto a la legalidad. Trabajo colaborativo y apertura al aprendizaje continuo.", "curso_propedeutico": "Aplicará cuando el diagnóstico de ingreso identifique brechas en matemáticas y programación. Población: aspirantes provenientes de bachilleratos no afines. Duración: 40 horas (4 semanas). Contenidos: Aritmética y álgebra para ingeniería; pensamiento lógico; introducción a programación (Python); fundamentos de redes; ciudadanía digital y nociones de ciberseguridad. Evaluación: examen criterio-referido (70% mínimo) y evidencias prácticas. Vinculación: articulado con Programación I, Matemáticas I y Fundamentos de Redes.", "modalidad_educativa": "Mixta", "antecedente_academico": "Bachillerato general o tecnológico concluido, o Técnico Superior Universitario equivalente, preferentemente en áreas físico-matemáticas, informática, electrónica o afines.", "carga_horaria_a_la_semana": 40, "programa_de_investigacion": null, "clave_del_plan_de_estudios": "2025", "duracion_del_ciclo_escolar": "18 semanas efectivas por ciclo semestral", "fines_de_aprendizaje_o_formacion": "El plan de estudios se orienta al desarrollo de competencias profesionales para diseñar, implementar y gestionar sistemas ciberfísicos y de ciberseguridad en organizaciones públicas y privadas, con enfoque de resultados y apego normativo. Al concluir, el egresado será capaz de: Analizar arquitecturas de sistemas ciberfísicos, redes y servicios para identificar riesgos, amenazas y vulnerabilidades. Diseñar estrategias de ciberseguridad basadas en estándares (ISO/IEC 27001, NIST, MITRE ATT&CK) y en principios de ingeniería segura. Implementar controles técnicos y administrativos, incluyendo criptografía aplicada, segmentación de redes, endurecimiento de sistemas y gestión de identidades. Programar y modelar sistemas embebidos y de IoT con prácticas de seguridad por diseño. Administrar infraestructuras de TI/OT y plataformas de monitoreo (SIEM, IDS/IPS) para la detección y respuesta a incidentes. Evaluar la conformidad normativa y el riesgo tecnológico mediante auditorías técnicas y de cumplimiento. Integrar metodologías de ciencia de datos e inteligencia artificial para la detección de anomalías y ciberamenazas. Simular, probar y validar el desempeño y la resiliencia de sistemas cibernéticos en entornos controlados (cyber range/laboratorios). Gestionar proyectos tecnológicos aplicando enfoques ágiles y de calidad, con perspectiva ética y de sostenibilidad. Comunicar hallazgos técnicos y recomendaciones a audiencias técnicas y directivas, sustentando decisiones con evidencia.", "total_de_ciclos_del_plan_de_estudios": "8 a 10 ciclos (semestres)", "sustento_teorico_del_modelo_curricular": "Se adopta un modelo por competencias y resultados de aprendizaje, con base en: Constructivismo y aprendizaje situado para problemas reales. Taxonomía de Bloom para progresión cognitiva. OBE/ABET para trazabilidad de resultados de programa-asignatura. CDIO (Concebir-Diseñar-Implementar-Operar) en proyectos integradores. Evaluación auténtica mediante rúbricas y desempeño en laboratorios físicos y virtuales.", "justificacion_de_la_propuesta_curricular": "La modalidad mixta combina sesiones presenciales/laboratorio y trabajo en línea para garantizar pertinencia, flexibilidad espacio-temporal y autogestión del aprendizaje. Plataformas tecnológicas: LMS institucional (p. ej., Moodle 4.x) con repositorio de contenidos, foros, cuestionarios, rúbricas y analítica; videoconferencia para sesiones sincrónicas; repositorios de código (Git) y laboratorios virtuales (simuladores de redes y ciberseguridad, sandboxes y máquinas virtuales) articulados con las asignaturas. Distribución horaria: 40-60% de horas bajo conducción (presencial o síncrona) y 60-40% de horas independientes (asíncronas) según la naturaleza de cada asignatura. Funcionalidad con instalaciones: laboratorios de redes, ciberseguridad e IoT; cyber range institucional; aulas de cómputo; todos con políticas de seguridad, bitácoras y control de acceso. Estrategias de aprendizaje: aula invertida, aprendizaje basado en problemas/proyectos, retos de blue/red team, estudios de caso y simulaciones. Evaluación: tareas prácticas en plataforma, laboratorios verificados, exámenes en línea con supervisión, presentaciones y defensa técnica. Perfil docente: ingenierías afines con experiencia en TI/OT, certificaciones deseables (CCNA/Network Security, ISO 27001 LA/LI, CEH o equivalentes) y dominio de herramientas LMS.", "administracion_y_operatividad_del_plan_de_estudios": "El plan es de diseño flexible con seriación académica por competencias y créditos. Se cursa en 8 a 10 semestres con un total referencial de 320 créditos (≥300 conforme al Art. 10). La operación académica considera: Carga por ciclo: de 5 a 7 asignaturas por semestre. Promedio semanal: 18-22 horas bajo conducción y 18-22 horas independientes (carga máxima 40 h/sem). Modalidad mixta: al menos el 40% de las horas del programa son bajo conducción de un académico (≥960 h totales); el resto en actividades independientes mediadas por plataforma. Seriación típica: Programación I → Programación II → Estructuras de Datos → Sistemas Operativos; Matemáticas I → Matemáticas II → Probabilidad y Estadística; Redes I → Redes II → Seguridad en Redes; Control I → Sistemas Embebidos → IoT Seguro. Componentes de formación: básica (matemáticas, ciencias y programación), profesional (redes, sistemas, control, seguridad), terminal optativa (ciberdefensa, forense, nube, OT/SCADA), Proyecto Integrador y Estancia/Residencia. Tutoría y asesoría: obligatoria el primer año y bajo demanda en cursos superiores. Servicio social: 480 horas conforme a normativa aplicable. Acreditación y titulación: por créditos y opción de titulación por examen general, proyecto aplicado, portafolio de evidencias o tesis.", "propuesta_de_evaluacion_periodica_del_plan_de_estudios": "La evaluación del plan se realizará con una periodicidad bienal y cortes anuales, mediante: Método: ciclo PDCA con evidencias OBE: mapeo resultados de programa-asignatura, rúbricas, portafolios y KPIs institucionales. Instrumentos: encuestas a estudiantes y egresados, panel de empleadores, análisis de EGEL/EXANI (cuando aplique), analítica del LMS, informes de prácticas/residencias y auditorías académicas internas. Indicadores: eficiencia terminal, tiempo de titulación, empleabilidad, resultados de aprendizaje, certificaciones profesionales obtenidas, producción de proyectos y patentes/prototipos. Vinculación sectorial: Consejo Consultivo con industria (TI/OT, energía, manufactura, finanzas) para actualización de contenidos al menos cada 2 años. Uso de resultados: actualización de programas, seriación y recursos didácticos; capacitación docente; adecuaciones a laboratorios y plataforma."}', '18f49b67-8077-4371-be6e-2019a3be3562', true, 'IA', '{}', NULL, NULL, '2026-01-09 21:48:48.404283+00', '2026-01-09 21:48:48.404283+00'),
- ('0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '8208da08-d549-4359-8865-9d806bc54f19', '69fb2b77-5a95-47e0-bf1f-389d384200e4', 'Ingeniería en Cibernética y Ciberseguridad (2025)', 'Licenciatura', 'Semestre', 8, '{"nivel": "Licenciatura", "nombre": "Ingeniería Cibernética", "area_de_estudio": "Tecnologías de la Información y la Comunicación (CMPE 2016)", "perfil_de_egreso": "Conocimientos: redes, sistemas, criptografía. Habilidades: pentesting, hardening, respuesta a incidentes. Actitudes: ética, responsabilidad.", "diseno_curricular": "Rígido", "perfil_de_ingreso": "Pensamiento lógico, bases de álgebra, nociones de programación y comunicación escrita.", "curso_propedeutico": null, "modalidad_educativa": "Escolar", "antecedente_academico": "Bachillerato concluido; afinidad con matemáticas y computación.", "carga_horaria_a_la_semana": 40, "programa_de_investigacion": null, "clave_del_plan_de_estudios": "2026-CIB-ING", "duracion_del_ciclo_escolar": "16", "fines_de_aprendizaje_o_formacion": "Formar profesionales capaces de diseñar, implementar y asegurar sistemas ciberfísicos y plataformas digitales.", "total_de_ciclos_del_plan_de_estudios": "8", "sustento_teorico_del_modelo_curricular": "Enfoque por competencias, aprendizaje basado en problemas y laboratorios.", "justificacion_de_la_propuesta_curricular": null, "administracion_y_operatividad_del_plan_de_estudios": "Plan rígido con cargas sugeridas por semestre; asignaturas seriadas por áreas.", "propuesta_de_evaluacion_periodica_del_plan_de_estudios": "Revisión anual por comité académico; actualización por tendencias y retroalimentación de egresados."}', '18f49b67-8077-4371-be6e-2019a3be3562', true, 'MANUAL', '{"seed": true, "version": "2026-01-08"}', '11111111-1111-1111-1111-111111111111', '11111111-1111-1111-1111-111111111111', '2026-01-09 13:45:37.784586+00', '2026-01-21 19:38:38.888504+00');
+INSERT INTO "public"."planes_estudio" ("id", "carrera_id", "estructura_id", "nombre", "nivel", "tipo_ciclo", "numero_ciclos", "datos", "estado_actual_id", "activo", "tipo_origen", "meta_origen", "creado_por", "actualizado_por", "creado_en", "actualizado_en", "conversation_id") VALUES
+ ('4660b774-a6e2-44f4-a340-747b41235c7c', '089cdeda-d557-4a57-b49f-eb44921dfa3a', '69fb2b77-5a95-47e0-bf1f-389d384200e4', 'Ingeniería en Sistemas Domóticos (2027)', 'Otro', 'Semestre', 9, '{"nivel": "Bachillerato", "nombre": "Especialización en Gravedad Cuántica de Bucles", "area_de_estudio": "Ciencias Físico-Matemáticas", "perfil_de_egreso": "La Specialización en Gravedad Cuántica de Bucles formará a un especialista capaz de integrar creativamente conocimientos avanzados en física, matemáticas y computación para enfrentar problemas teóricos y fenomenológicos en la intersección de la relatividad general y la mecánica cuántica. Al finalizar el programa, el egresado será capaz de: 1) Comprender y aplicar los fundamentos de la gravedad cuántica de bucles, incluyendo su formulación canónica, las variables de Ashtekar y el formalismo de redes de espín; 2) Desarrollar modelos y simulaciones rigurosas de fenómenos cuántico-gravitacionales, empleando métodos numéricos y herramientas de cómputo científico para analizar redes y espumas de espín, así como su relación con la cosmología cuántica; 3) Utilizar y conectar conceptos avanzados de geometría diferencial, teoría de grupos de Lie, representaciones y espinores para la construcción de estados cuánticos geométricos; 4) Resolver y comunicar de manera crítica problemas de frontera en física teórica contemporánea, integrando procesos de modelado matemático, análisis computacional y validación fenomenológica; 5) Evaluar y proponer soluciones innovadoras en entornos de trabajo colaborativo, mostrando habilidad para desarrollar proyectos científicos reproducibles bajo principios éticos y de buenas prácticas en la investigación; 6) Integrarse a comunidades científicas y continuar con estudios de posgrado o desempeñarse en entornos interdisciplinares que involucren física matemática, simulación, cosmología, información cuántica o modelado de sistemas complejos.", "diseno_curricular": "Rígido", "perfil_de_ingreso": "El aspirante debe haber concluido el bachillerato con perfil Área 1 (Ciencias Físico-Matemáticas), lo que implica sólidas bases en matemáticas, física, química, razonamiento lógico y competencia en análisis de problemas. Se espera capacidad para el aprendizaje autónomo y familiaridad con herramientas y conceptos científicos y tecnológicos, así como interés en la resolución de problemas complejos y trabajo experimental.", "curso_propedeutico": "No tiene", "modalidad_educativa": "Escolar", "antecedente_academico": "Bachillerato en Ciencias Físico-Matemáticas, con orientación en matemáticas y física; requisito previo para ingreso a la especialización. El plan asume que el aspirante ha concluido un bachillerato con áreas de Ciencias Físico-Matemáticas.", "carga_horaria_a_la_semana": 27, "programa_de_investigacion": "No aplica", "clave_del_plan_de_estudios": "2020", "duracion_del_ciclo_escolar": "16 semanas", "fines_de_aprendizaje_o_formacion": "Analizar fundamentos de la gravedad cuántica de bucles y su relación con la relatividad general; Diseñar modelos computacionales para explorar redes de espín y espumas cuánticas, con enfoque en cosmología cuántica; Evaluar resultados numéricos mediante criterios de reproducibilidad y validación fenomenológica; Comunicar resultados científicos de forma clara, estructurada y técnica; Integrar conceptos de domótica, electrónica, control y sistemas computacionales mediante herramientas de simulación para proyectos interdisciplinarios y el desarrollo de asistentes virtuales; Aplicar herramientas de cómputo para la creación de proyectos con código reproducible y documentación adecuada; Desarrollar capacidades de trabajo en equipo, ética científica y buenas prácticas en investigación.", "total_de_ciclos_del_plan_de_estudios": "9", "sustento_teorico_del_modelo_curricular": "No aplica", "justificacion_de_la_propuesta_curricular": "No aplica", "administracion_y_operatividad_del_plan_de_estudios": "La administración y operatividad del plan de estudios se ubica bajo la Dirección de Asuntos Académicos - Anexo 1; modalidad escolar; estructura rígida con 9 ciclos; carga horaria semanal de 27 horas; responsable: Lic. María González Pérez, Directora Académica; procesos de revisión y actualización conforme a la Propuesta de Evaluación Periódica y a los lineamientos del Acuerdo 17/11/17.", "propuesta_de_evaluacion_periodica_del_plan_de_estudios": "Nivel de evaluación curricular interna: diagnóstico de estructura y operación. Análisis técnico-pedagógico del planteamiento curricular vigente; consulta a directivos del área académica para valorar problemáticas de formación y operación; consulta a docentes sobre problemáticas de formación, operación y necesidades disciplinarias; estudio de opinión de estudiantes sobre la formación y operación. Nivel de evaluación curricular externa: diagnóstico de impacto y prácticas profesionales. Análisis del estado del conocimiento en campos disciplinarios vinculados al programa, comparación de planes y tendencias en criterios de organismos evaluadores; estudio con egresados y empleadores para valorar inserción laboral y requerimientos actuales; recopilación de criterios y reformas necesarias para mantener el programa actualizado y acorde con la disciplina."}', '18f49b67-8077-4371-be6e-2019a3be3562', true, 'IA', '{"iaConfig": {"usarMCP": false, "notasAdicionales": "Agrega a los asistentes virtuales", "descripcionEnfoque": "Carrera relacionada con la domótica, electrónica, control y sistemas computacionales"}, "referencias": {"repositoriosIds": [], "archivosReferenciaIds": []}, "generado_por": "ai_generate_plan", "ai_structured": {"model": "gpt-5-nano-2025-08-07", "usage": {"input_tokens": 9812, "total_tokens": 19204, "output_tokens": 9392, "input_tokens_details": {"cached_tokens": 0}, "output_tokens_details": {"reasoning_tokens": 8256}}, "responseId": "resp_0b1339b15ea08d51006973b4892a20819c954b7fe00a35e1ed", "conversationId": null}}', NULL, NULL, '2026-01-23 17:50:00.85346+00', '2026-01-23 22:00:33.119999+00', NULL),
+ ('c90aebbb-454e-4a8e-b2a3-99dd1c200e86', '8208da08-d549-4359-8865-9d806bc54f19', '69fb2b77-5a95-47e0-bf1f-389d384200e4', 'Ingeniería en Cibernética y Ciberseguridad (2026)', 'Licenciatura', 'Semestre', 9, '{"nivel": "Licenciatura", "nombre": "Ingeniería Cibernética y Ciberseguridad", "area_de_estudio": "5. Ingeniería, manufactura y construcción (CMPE 2016)", "perfil_de_egreso": "Al finalizar, el egresado demostrará los siguientes atributos: Conocimientos Fundamentos de matemáticas, física y probabilidad aplicados a señales, control y comunicaciones. Arquitecturas de redes, sistemas operativos, virtualización, servicios en la nube e integración TI/OT. Modelado y control de sistemas ciberfísicos y embebidos, protocolos industriales e IoT. Criptografía aplicada, gestión de identidades, gobierno y gestión de seguridad de la información. Normativa y marcos de referencia: ISO/IEC 27001/27002, NIST CSF/800-53, MITRE ATT&CK, OWASP, ENS (referencial), legislación mexicana aplicable en protección de datos y delitos informáticos. Auditoría de seguridad, pruebas de penetración éticas, respuesta a incidentes y continuidad del negocio. Gestión de proyectos, economía de la ciberseguridad y ética profesional. HabilidadesDiseñar y desplegar arquitecturas seguras para redes, nube y sistemas ciberfísicos. Programar en C/C++, Python y scripts para automatización, pruebas y análisis. Configurar y operar firewalls, IDS/IPS, SIEM, WAF, VPN, segmentación y Zero Trust básico. Conducir análisis de riesgo, planes de tratamiento y auditorías técnicas y de cumplimiento. Orquestar procesos de detección, respuesta y recuperación ante incidentes. Desarrollar prototipos de IoT seguro y realizar hardening de dispositivos embebidos. Comunicar resultados técnicos mediante informes ejecutivos y presentaciones efectivas. Actitudes y valoresÉtica, responsabilidad social y legalidad en el manejo de información y en pruebas de seguridad. Aprendizaje autónomo y actualización permanente. Trabajo colaborativo, liderazgo y orientación a resultados. Respeto a la diversidad, perspectiva de género e inclusión.", "diseno_curricular": "Flexible", "perfil_de_ingreso": " ConocimientosMatemáticas de bachillerato: álgebra, funciones y trigonometría. Ofimática e informática básica; navegación segura en Internet. Inglés técnico básico (lectura de manuales). HabilidadesPensamiento lógico-matemático y resolución de problemas. Comunicación escrita y oral. Gestión del tiempo y aprendizaje autónomo. ActitudesInterés por la tecnología, la seguridad digital y la innovación. Ética, responsabilidad y respeto a la legalidad. Trabajo colaborativo y apertura al aprendizaje continuo.", "curso_propedeutico": "Aplicará cuando el diagnóstico de ingreso identifique brechas en matemáticas y programación. Población: aspirantes provenientes de bachilleratos no afines. Duración: 40 horas (4 semanas). Contenidos: Aritmética y álgebra para ingeniería; pensamiento lógico; introducción a programación (Python); fundamentos de redes; ciudadanía digital y nociones de ciberseguridad. Evaluación: examen criterio-referido (70% mínimo) y evidencias prácticas. Vinculación: articulado con Programación I, Matemáticas I y Fundamentos de Redes.", "modalidad_educativa": "Mixta", "antecedente_academico": "Bachillerato general o tecnológico concluido, o Técnico Superior Universitario equivalente, preferentemente en áreas físico-matemáticas, informática, electrónica o afines.", "carga_horaria_a_la_semana": 40, "programa_de_investigacion": null, "clave_del_plan_de_estudios": "2025", "duracion_del_ciclo_escolar": "18 semanas efectivas por ciclo semestral", "fines_de_aprendizaje_o_formacion": "El plan de estudios se orienta al desarrollo de competencias profesionales para diseñar, implementar y gestionar sistemas ciberfísicos y de ciberseguridad en organizaciones públicas y privadas, con enfoque de resultados y apego normativo. Al concluir, el egresado será capaz de: Analizar arquitecturas de sistemas ciberfísicos, redes y servicios para identificar riesgos, amenazas y vulnerabilidades. Diseñar estrategias de ciberseguridad basadas en estándares (ISO/IEC 27001, NIST, MITRE ATT&CK) y en principios de ingeniería segura. Implementar controles técnicos y administrativos, incluyendo criptografía aplicada, segmentación de redes, endurecimiento de sistemas y gestión de identidades. Programar y modelar sistemas embebidos y de IoT con prácticas de seguridad por diseño. Administrar infraestructuras de TI/OT y plataformas de monitoreo (SIEM, IDS/IPS) para la detección y respuesta a incidentes. Evaluar la conformidad normativa y el riesgo tecnológico mediante auditorías técnicas y de cumplimiento. Integrar metodologías de ciencia de datos e inteligencia artificial para la detección de anomalías y ciberamenazas. Simular, probar y validar el desempeño y la resiliencia de sistemas cibernéticos en entornos controlados (cyber range/laboratorios). Gestionar proyectos tecnológicos aplicando enfoques ágiles y de calidad, con perspectiva ética y de sostenibilidad. Comunicar hallazgos técnicos y recomendaciones a audiencias técnicas y directivas, sustentando decisiones con evidencia.", "total_de_ciclos_del_plan_de_estudios": "8 a 10 ciclos (semestres)", "sustento_teorico_del_modelo_curricular": "Se adopta un modelo por competencias y resultados de aprendizaje, con base en: Constructivismo y aprendizaje situado para problemas reales. Taxonomía de Bloom para progresión cognitiva. OBE/ABET para trazabilidad de resultados de programa-asignatura. CDIO (Concebir-Diseñar-Implementar-Operar) en proyectos integradores. Evaluación auténtica mediante rúbricas y desempeño en laboratorios físicos y virtuales.", "justificacion_de_la_propuesta_curricular": "La modalidad mixta combina sesiones presenciales/laboratorio y trabajo en línea para garantizar pertinencia, flexibilidad espacio-temporal y autogestión del aprendizaje. Plataformas tecnológicas: LMS institucional (p. ej., Moodle 4.x) con repositorio de contenidos, foros, cuestionarios, rúbricas y analítica; videoconferencia para sesiones sincrónicas; repositorios de código (Git) y laboratorios virtuales (simuladores de redes y ciberseguridad, sandboxes y máquinas virtuales) articulados con las asignaturas. Distribución horaria: 40-60% de horas bajo conducción (presencial o síncrona) y 60-40% de horas independientes (asíncronas) según la naturaleza de cada asignatura. Funcionalidad con instalaciones: laboratorios de redes, ciberseguridad e IoT; cyber range institucional; aulas de cómputo; todos con políticas de seguridad, bitácoras y control de acceso. Estrategias de aprendizaje: aula invertida, aprendizaje basado en problemas/proyectos, retos de blue/red team, estudios de caso y simulaciones. Evaluación: tareas prácticas en plataforma, laboratorios verificados, exámenes en línea con supervisión, presentaciones y defensa técnica. Perfil docente: ingenierías afines con experiencia en TI/OT, certificaciones deseables (CCNA/Network Security, ISO 27001 LA/LI, CEH o equivalentes) y dominio de herramientas LMS.", "administracion_y_operatividad_del_plan_de_estudios": "El plan es de diseño flexible con seriación académica por competencias y créditos. Se cursa en 8 a 10 semestres con un total referencial de 320 créditos (≥300 conforme al Art. 10). La operación académica considera: Carga por ciclo: de 5 a 7 asignaturas por semestre. Promedio semanal: 18-22 horas bajo conducción y 18-22 horas independientes (carga máxima 40 h/sem). Modalidad mixta: al menos el 40% de las horas del programa son bajo conducción de un académico (≥960 h totales); el resto en actividades independientes mediadas por plataforma. Seriación típica: Programación I → Programación II → Estructuras de Datos → Sistemas Operativos; Matemáticas I → Matemáticas II → Probabilidad y Estadística; Redes I → Redes II → Seguridad en Redes; Control I → Sistemas Embebidos → IoT Seguro. Componentes de formación: básica (matemáticas, ciencias y programación), profesional (redes, sistemas, control, seguridad), terminal optativa (ciberdefensa, forense, nube, OT/SCADA), Proyecto Integrador y Estancia/Residencia. Tutoría y asesoría: obligatoria el primer año y bajo demanda en cursos superiores. Servicio social: 480 horas conforme a normativa aplicable. Acreditación y titulación: por créditos y opción de titulación por examen general, proyecto aplicado, portafolio de evidencias o tesis.", "propuesta_de_evaluacion_periodica_del_plan_de_estudios": "La evaluación del plan se realizará con una periodicidad bienal y cortes anuales, mediante: Método: ciclo PDCA con evidencias OBE: mapeo resultados de programa-asignatura, rúbricas, portafolios y KPIs institucionales. Instrumentos: encuestas a estudiantes y egresados, panel de empleadores, análisis de EGEL/EXANI (cuando aplique), analítica del LMS, informes de prácticas/residencias y auditorías académicas internas. Indicadores: eficiencia terminal, tiempo de titulación, empleabilidad, resultados de aprendizaje, certificaciones profesionales obtenidas, producción de proyectos y patentes/prototipos. Vinculación sectorial: Consejo Consultivo con industria (TI/OT, energía, manufactura, finanzas) para actualización de contenidos al menos cada 2 años. Uso de resultados: actualización de programas, seriación y recursos didácticos; capacitación docente; adecuaciones a laboratorios y plataforma."}', '18f49b67-8077-4371-be6e-2019a3be3562', true, 'IA', '{"response_id": "lsadjf"}', NULL, NULL, '2026-01-09 21:48:48.404283+00', '2026-01-23 22:18:37.320091+00', NULL),
+ ('ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', '089cdeda-d557-4a57-b49f-eb44921dfa3a', '69fb2b77-5a95-47e0-bf1f-389d384200e4', 'Ingeniería Mecatrónica (2026)', 'Licenciatura', 'Semestre', 9, '{"nombre_autorizado_de_la_institucion": "Agregando nuevos datos con persistencia"}', '18f49b67-8077-4371-be6e-2019a3be3562', true, 'IA', '{"iaConfig": {"usarMCP": false, "notasAdicionales": "", "descripcionEnfoque": "Dame un plan de estudios realista y enfocado en física médica"}, "referencias": {"repositoriosIds": [], "archivosReferenciaIds": []}, "generado_por": "ai_generate_plan", "ai_structured": {"model": "gpt-5-nano-2025-08-07", "usage": {"input_tokens": 202327, "total_tokens": 209102, "output_tokens": 6775, "input_tokens_details": {"cached_tokens": 0}, "output_tokens_details": {"reasoning_tokens": 5696}}, "responseId": "resp_08633e9c3f0021c000697299eb9c2881a2a3bdc31d43c4bf0d", "conversationId": null}}', NULL, NULL, '2026-01-22 21:44:28.093643+00', '2026-01-29 21:58:12.447523+00', NULL),
+ ('f6280a08-226d-480b-ab67-561e4657bfc4', 'b56eb209-245d-4258-a6a7-9dcb1d231cb4', '69fb2b77-5a95-47e0-bf1f-389d384200e4', 'Ingeniería Económica y Financiera', 'Licenciatura', 'Semestre', 9, '{}', '18f49b67-8077-4371-be6e-2019a3be3562', true, 'MANUAL', '{}', NULL, NULL, '2026-01-30 14:33:50.818+00', '2026-01-30 14:33:50.818+00', NULL),
+ ('79fba458-aea4-4ca8-ab76-943ede479f15', 'b56eb209-245d-4258-a6a7-9dcb1d231cb4', '69fb2b77-5a95-47e0-bf1f-389d384200e4', 'prueba', 'Licenciatura', 'Semestre', 9, '{}', '18f49b67-8077-4371-be6e-2019a3be3562', true, 'MANUAL', '{}', NULL, NULL, '2026-01-30 14:44:48.312+00', '2026-01-30 14:44:48.312+00', NULL),
+ ('0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '8208da08-d549-4359-8865-9d806bc54f19', '69fb2b77-5a95-47e0-bf1f-389d384200e4', 'Ingeniería en Cibernética y Ciberseguridad (2025)', 'Maestría', 'Semestre', 8, '{"nivel": "Licenciatura", "nombre": "Ingeniería Cibernética", "area_de_estudio": "Tecnologías de la Información y la Comunicación (CMPE 2016)", "perfil_de_egreso": "Conocimientos: redes, sistemas, criptografía. Habilidades: pentesting, hardening, respuesta a incidentes. Actitudes: ética, responsabilidad.", "diseno_curricular": "Rígido", "perfil_de_ingreso": "Pensamiento lógico, bases de álgebra, nociones de programación y comunicación escrita.", "curso_propedeutico": null, "modalidad_educativa": "Probando guardado", "antecedente_academico": "Bachillerato concluido; afinidad con matemáticas y computación.", "carga_horaria_a_la_semana": 40, "programa_de_investigacion": null, "clave_del_plan_de_estudios": "2026-CIB-ING", "duracion_del_ciclo_escolar": "16", "fines_de_aprendizaje_o_formacion": "Formar profesionales capaces de diseñar, implementar y asegurar sistemas ciberfísicos y plataformas digitales.", "nombre_autorizado_de_la_institucion": "Agregando informacion persistente", "total_de_ciclos_del_plan_de_estudios": "8", "sustento_teorico_del_modelo_curricular": "Enfoque por competencias, aprendizaje basado en problemas y laboratorios.", "justificacion_de_la_propuesta_curricular": null, "administracion_y_operatividad_del_plan_de_estudios": "Plan rígido con cargas sugeridas por semestre; asignaturas seriadas por áreas.", "propuesta_de_evaluacion_periodica_del_plan_de_estudios": "Revisión anual por comité académico; actualización por tendencias y retroalimentación de egresados."}', '18f49b67-8077-4371-be6e-2019a3be3562', true, 'MANUAL', '{"seed": true, "version": "2026-01-08"}', '11111111-1111-1111-1111-111111111111', '11111111-1111-1111-1111-111111111111', '2026-01-09 13:45:37.784586+00', '2026-01-30 20:18:43.823019+00', '123'),
+ ('c5d80b6d-5d3d-4730-a4d5-8eaec3287910', '20593041-d605-4174-8404-4b152041eece', '69fb2b77-5a95-47e0-bf1f-389d384200e4', 'Derecho', 'Licenciatura', 'Semestre', 10, '{}', '18f49b67-8077-4371-be6e-2019a3be3562', true, 'MANUAL', '{}', NULL, NULL, '2026-01-30 21:03:52.036+00', '2026-01-30 21:03:52.035+00', NULL);
--
@@ -1983,8 +2088,8 @@ INSERT INTO "public"."lineas_plan" ("id", "plan_estudio_id", "nombre", "orden",
-- Data for Name: asignaturas; Type: TABLE DATA; Schema: public; Owner: postgres
--
-INSERT INTO "public"."asignaturas" ("id", "plan_estudio_id", "estructura_id", "facultad_propietaria_id", "codigo", "nombre", "tipo", "creditos", "horas_semana", "numero_ciclo", "linea_plan_id", "orden_celda", "datos", "contenido_tematico", "tipo_origen", "meta_origen", "creado_por", "actualizado_por", "creado_en", "actualizado_en") VALUES
- ('9d4dda6a-488f-428a-8a07-38081592a641', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '7856e9cc-9ac1-4a31-b93a-0624d4c5e1de', '155b5fe7-9e09-420f-8d9d-ddd8219f193d', 'CIB-CS-401', 'OffSec (Seguridad Ofensiva)', 'OBLIGATORIA', 8, 6, 4, 'c261fbce-893a-4900-b03c-2f3ad3ce4987', 1, '{"nombre": "Inteligencia Artificial Aplicada y Ética Profesional", "descripcion": "Esta asignatura ofrece una visión integral sobre el funcionamiento de los sistemas de IA, su implementación en entornos productivos y el análisis crítico de las implicaciones éticas y sociales de su uso.", "perfil_de_egreso": "Al concluir la materia, el egresado habrá desarrollado los siguientes atributos: \n\nCONOCIMIENTOS: Comprende los fundamentos de Machine Learning, procesamiento de lenguaje natural y los marcos legales actuales sobre privacidad de datos. \nHABILIDADES: Capacidad para integrar herramientas de IA generativa en flujos de trabajo profesionales y evaluar sesgos en modelos algorítmicos. \nACTITUDES: Actúa con responsabilidad social, promoviendo la transparencia y la equidad en el desarrollo tecnológico.", "fines_de_aprendizaje_o_formacion": "Al finalizar el curso, el estudiante será capaz de diseñar e implementar soluciones básicas basadas en inteligencia artificial que optimicen procesos organizacionales, asegurando en todo momento el cumplimiento de marcos normativos y principios éticos internacionales para el manejo responsable de datos y algoritmos."}', '[{"temas": ["Ética y legalidad", "PTES/OSSTMM (visión general)", "Alcance y reglas"], "titulo": "Fundamentos y metodología", "unidad": 1}, {"temas": ["OSINT", "Descubrimiento de servicios", "Enumeración"], "titulo": "Reconocimiento y enumeración", "unidad": 2}, {"temas": ["Vulnerabilidades comunes", "Explotación controlada", "Post-explotación básica"], "titulo": "Explotación en laboratorio", "unidad": 3}, {"temas": ["Evidencias", "Severidad", "Recomendaciones"], "titulo": "Reporte y remediación", "unidad": 4}]', 'MANUAL', '{"seed": true}', '11111111-1111-1111-1111-111111111111', '11111111-1111-1111-1111-111111111111', '2026-01-09 13:45:37.784586+00', '2026-01-13 20:41:33.238058+00');
+INSERT INTO "public"."asignaturas" ("id", "plan_estudio_id", "estructura_id", "codigo", "nombre", "tipo", "creditos", "horas_semana", "numero_ciclo", "linea_plan_id", "orden_celda", "datos", "contenido_tematico", "tipo_origen", "meta_origen", "creado_por", "actualizado_por", "creado_en", "actualizado_en") VALUES
+ ('9d4dda6a-488f-428a-8a07-38081592a641', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '7856e9cc-9ac1-4a31-b93a-0624d4c5e1de', 'CIB-CS-401', 'OffSec (Seguridad Ofensiva)', 'OBLIGATORIA', 10, 6, 5, 'c261fbce-893a-4900-b03c-2f3ad3ce4987', 1, '{"ciclo": "Hola 123", "contenido_tematico": "Enunciar, en idioma español, los temas y subtemas que se abordarán en la asignatura o unidad de aprendizaje; estructurados de manera lógica y secuencial y numerados en orden jerárquico. Éstos deberán ser acordes con la carga horaria asignada, sustentar la denominación de la asignatura y contribuir al logro de los fines de aprendizaje.", "clave_de_la_asignatura": "Anotar la clave que identifica a la asignatura o unidad de aprendizaje; la cual debe coincidir con la señalada en el mapa curricular.", "criterios_de_evaluacion": "Enlistar las actividades o productos que considerarán para valorar los aprendizajes logrados por los estudiantes, señalar el porcentaje para cada uno, de tal manera que sumen el 100%. Éstos deberán ser acordes con las actividades de aprendizaje.", "fines_de_aprendizaje_o_formacion": "Describir los aprendizajes y fines que alcanzará el estudiante al concluir la asignatura. Deberá sustentar la denominación de la asignatura, vincularse con los contenidos temáticos y coadyuvar al logro del fin de aprendizaje y perfil de egreso del plan de estudios.", "modalidades_tecnologicas_e_informaticas": "Describir los recursos tecnológicos y de comunicación que se emplearán para el desarrollo del proceso de enseñanza-aprendizaje de la asignatura. Para las modalidades no escolarizada y mixta, este elemento es obligatorio.", "actividades_de_aprendizaje_independientes": "Señalar las actividades de aprendizaje que desarrollará el estudiante de manera independiente. Éstas deberán ser acordes con la naturaleza de la asignatura y la modalidad educativa.", "denominacion_de_la_asignatura_o_unidad_de_aprendizaje": "Señalar el nombre de la asignatura o unidad de aprendizaje de igual manera que en el mapa curricular.", "actividades_de_aprendizaje_bajo_conduccion_de_un_academico": "Señalar las actividades de aprendizaje que desarrollará el estudiante en las horas-clase. Éstas deberán ser acordes con la naturaleza de la asignatura, modalidad educativa e instalaciones empleadas, además de contribuir al logro de los fines de aprendizaje."}', '[{"temas": ["Ética y legalidad", "PTES/OSSTMM (visión general)", "Alcance y reglas"], "titulo": "Fundamentos y metodología", "unidad": 1}, {"temas": ["OSINT", "Descubrimiento de servicios", "Enumeración"], "titulo": "Reconocimiento y enumeración", "unidad": 2}, {"temas": ["Vulnerabilidades comunes", "Explotación controlada", "Post-explotación básica"], "titulo": "Explotación en laboratorio", "unidad": 3}, {"temas": ["Evidencias", "Severidad", "Recomendaciones"], "titulo": "Reporte y remediación", "unidad": 4}]', 'MANUAL', '{"seed": true}', '11111111-1111-1111-1111-111111111111', '11111111-1111-1111-1111-111111111111', '2026-01-09 13:45:37.784586+00', '2026-01-30 21:38:02.89992+00');
--
@@ -2008,8 +2113,54 @@ INSERT INTO "public"."cambios_asignatura" ("id", "asignatura_id", "cambiado_por"
-- Data for Name: cambios_plan; Type: TABLE DATA; Schema: public; Owner: postgres
--
-INSERT INTO "public"."cambios_plan" ("id", "plan_estudio_id", "cambiado_por", "cambiado_en", "tipo", "campo", "valor_anterior", "valor_nuevo", "interaccion_ia_id") VALUES
- ('9750ab31-f898-4e2e-971d-07e21bea6d50', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-09 13:45:37.784586+00', 'CREACION', 'datos', NULL, '{"nivel": "Licenciatura", "nombre": "Ingeniería Cibernética", "area_de_estudio": "Tecnologías de la Información y la Comunicación (CMPE 2016)", "perfil_de_egreso": "Conocimientos: redes, sistemas, criptografía. Habilidades: pentesting, hardening, respuesta a incidentes. Actitudes: ética, responsabilidad.", "diseno_curricular": "Rígido", "perfil_de_ingreso": "Pensamiento lógico, bases de álgebra, nociones de programación y comunicación escrita.", "curso_propedeutico": null, "modalidad_educativa": "Escolar", "antecedente_academico": "Bachillerato concluido; afinidad con matemáticas y computación.", "carga_horaria_a_la_semana": 40, "programa_de_investigacion": null, "clave_del_plan_de_estudios": "2026-CIB-ING", "duracion_del_ciclo_escolar": "16", "fines_de_aprendizaje_o_formacion": "Formar profesionales capaces de diseñar, implementar y asegurar sistemas ciberfísicos y plataformas digitales.", "total_de_ciclos_del_plan_de_estudios": "8", "sustento_teorico_del_modelo_curricular": "Enfoque por competencias, aprendizaje basado en problemas y laboratorios.", "justificacion_de_la_propuesta_curricular": null, "administracion_y_operatividad_del_plan_de_estudios": "Plan rígido con cargas sugeridas por semestre; asignaturas seriadas por áreas.", "propuesta_de_evaluacion_periodica_del_plan_de_estudios": "Revisión anual por comité académico; actualización por tendencias y retroalimentación de egresados."}', NULL);
+INSERT INTO "public"."cambios_plan" ("id", "plan_estudio_id", "cambiado_por", "cambiado_en", "tipo", "campo", "valor_anterior", "valor_nuevo", "response_id") VALUES
+ ('9750ab31-f898-4e2e-971d-07e21bea6d50', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-09 13:45:37.784586+00', 'CREACION', 'datos', NULL, '{"nivel": "Licenciatura", "nombre": "Ingeniería Cibernética", "area_de_estudio": "Tecnologías de la Información y la Comunicación (CMPE 2016)", "perfil_de_egreso": "Conocimientos: redes, sistemas, criptografía. Habilidades: pentesting, hardening, respuesta a incidentes. Actitudes: ética, responsabilidad.", "diseno_curricular": "Rígido", "perfil_de_ingreso": "Pensamiento lógico, bases de álgebra, nociones de programación y comunicación escrita.", "curso_propedeutico": null, "modalidad_educativa": "Escolar", "antecedente_academico": "Bachillerato concluido; afinidad con matemáticas y computación.", "carga_horaria_a_la_semana": 40, "programa_de_investigacion": null, "clave_del_plan_de_estudios": "2026-CIB-ING", "duracion_del_ciclo_escolar": "16", "fines_de_aprendizaje_o_formacion": "Formar profesionales capaces de diseñar, implementar y asegurar sistemas ciberfísicos y plataformas digitales.", "total_de_ciclos_del_plan_de_estudios": "8", "sustento_teorico_del_modelo_curricular": "Enfoque por competencias, aprendizaje basado en problemas y laboratorios.", "justificacion_de_la_propuesta_curricular": null, "administracion_y_operatividad_del_plan_de_estudios": "Plan rígido con cargas sugeridas por semestre; asignaturas seriadas por áreas.", "propuesta_de_evaluacion_periodica_del_plan_de_estudios": "Revisión anual por comité académico; actualización por tendencias y retroalimentación de egresados."}', NULL),
+ ('565ef79e-4a20-42cd-9aa9-41fa22fd476d', '4660b774-a6e2-44f4-a340-747b41235c7c', NULL, '2026-01-23 22:00:33.119999+00', 'ACTUALIZACION', 'nombre', '"Ingeniería en Sistemas Domóticos (2026)"', '"Ingeniería en Sistemas Domóticos (2027)"', NULL),
+ ('e37fb4cc-1280-4d71-8d97-23e8d5c3162c', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-23 22:37:05.875968+00', 'ACTUALIZACION', 'nivel', '"Licenciatura"', '"Maestría"', NULL),
+ ('4e1edec0-8723-403f-a856-6aa22505fc1f', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-23 22:39:07.266896+00', 'ACTUALIZACION', 'conversation_id', NULL, '"123"', NULL),
+ ('c9a74e54-653d-4e21-a961-98acd3efcd0c', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-27 15:34:16.6225+00', 'ACTUALIZACION_CAMPO', 'perfil_de_ingreso', '"Ingreso esperado para la Licenciatura en Física Médica: antecedentes académicos en bachillerato con fuerte formación en matemáticas y física (álgebra lineal, cálculo diferencial e integral, física general); habilidades de razonamiento lógico y resolución de problemas; aptitudes para trabajo colaborativo y cumplimiento de normas de seguridad y ética; interés en aplicaciones biomédicas y manejo básico de herramientas de laboratorio."', NULL, NULL),
+ ('82be7c12-5b7b-465e-9018-cea7f11f4213', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-27 15:34:16.6225+00', 'ACTUALIZACION_CAMPO', 'programa_de_investigacion', 'null', NULL, NULL),
+ ('36a747b7-1105-4f8c-b9f5-0e59c73b1173', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-27 15:34:16.6225+00', 'ACTUALIZACION_CAMPO', 'justificacion_de_la_propuesta_curricular', '"La necesidad de profesionales especializados en física médica que aporten diagnósticos precisos y tratamientos seguros justifica una formación que combine fundamentos teóricos sólidos con prácticas clínicas y laboratoriales. La modalidad mixta facilita la integración de laboratorios, simulaciones y aprendizaje autogestionado, manteniendo flexibilidad espacial y temporal para estudiantes trabajadores. Se incorporan plataformas tecnológicas, simuladores y experiencias clínicas supervisadas para asegurar la calidad educativa y la empleabilidad, alineándose con las metas y lineamientos del Acuerdo 17/11/17 y el marco de la SEP."', NULL, NULL),
+ ('ea313ce3-898f-40d5-9e6c-288d7f711009', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-27 15:34:16.6225+00', 'ACTUALIZACION_CAMPO', 'perfil_de_egreso', '"Al finalizar el plan, el egresado estará equipado para: 1) Conocer y comprender fundamentos teóricos y prácticos de la física médica aplicados a diagnóstico y tratamiento; 2) Desarrollar habilidades de evaluación y diseño de protocolos clínicos en radiología, medicina nuclear y radioterapia; 3) Aplicar técnicas de dosimetría, calibración de equipos y control de calidad en contextos médicos; 4) Interpretar resultados de imágenes y de pruebas de diagnóstico por imagen con rigor científico; 5) Actuar con ética profesional, seguridad de pacientes y manejo responsable de información clínica; 6) Trabajar de forma colaborativa en equipos interdisciplinarios para la mejora continua de procesos en medios de salud."', NULL, NULL),
+ ('76a0e4dc-d93d-409b-8ce5-97400efcc51d', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-27 15:34:16.6225+00', 'ACTUALIZACION_CAMPO', 'nivel', '"Licenciatura"', NULL, NULL),
+ ('6542239b-1d70-447c-a15f-569a9d7178b2', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-27 15:34:16.6225+00', 'ACTUALIZACION_CAMPO', 'propuesta_de_evaluacion_periodica_del_plan_de_estudios', '"La evaluación periódica del Plan de Estudio se realizará de manera anual y cada tres años se revisará en conjunto con comités de docentes, estudiantes y empleadores. Se emplearán rúbricas de competencias, evaluaciones prácticas, portafolios, indicadores de empleabilidad y calidad de servicios, con mecanismos de retroalimentación y acciones correctivas para actualizar contenidos, métodos pedagógicos y recursos tecnológicos. Los resultados guiarán ajustes curriculares y la mejora institucional."', NULL, NULL),
+ ('e4f4992f-149b-44a3-860d-eccbaf7cbb65', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-27 15:34:16.6225+00', 'ACTUALIZACION_CAMPO', 'carga_horaria_a_la_semana', '16', NULL, NULL),
+ ('56ae5448-0842-4b4f-97fe-5c29ca8fbd3f', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-27 15:34:16.6225+00', 'ACTUALIZACION_CAMPO', 'area_de_estudio', '"Ciencias Físicas – Física Médica (CMPE 2016: Física)"', NULL, NULL),
+ ('90154137-ae23-4017-813f-d30c7b90443b', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-27 15:34:16.6225+00', 'ACTUALIZACION_CAMPO', 'curso_propedeutico', '"Curso propedéutico de 4 semanas dirigido a reforzar fundamentos de cálculo, física general y conceptos básicos de biomedicina. Este curso se ofrece a la población con deficientes antecedentes en áreas de ciencias; su implementación se vincula como preparación para los cursos introductorios de la población clínica y técnica."', NULL, NULL),
+ ('9deff71b-4eaf-463e-be1b-9a8c6a127046', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-27 15:34:16.6225+00', 'ACTUALIZACION_CAMPO', 'administracion_y_operatividad_del_plan_de_estudios', '"La administración del plan estará a cargo de la Dirección Académica y un Comité de Planes y Programas. Existirá un Responsable de Carrera, coordinadores de laboratorio y tutores. Se establecerá un mapa curricular con asignaturas y unidades de aprendizaje, criterios de evaluación y un ciclo de revisión anual para ajustes conforme a necesidades regionales y nacionales. Se dispondrá de un Comité de Acreditación interna para garantizar la congruencia entre plan, laboratorio y clínica."', NULL, NULL),
+ ('0a6bd6f5-e376-42c2-a72f-e86d7f24701e', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-27 15:34:16.6225+00', 'ACTUALIZACION_CAMPO', 'sustento_teorico_del_modelo_curricular', '"El sustento teórico del plan se apoya en modelos de diseño curricular por competencias, aprendizaje basado en problemas y blended learning, con énfasis en la seguridad, la ética y la calidad en servicios de salud. Se integran marcos de referencia de educación superior y estándares SEP para garantizar pertinencia y rigor científico, así como la interoperabilidad con normativas de dispositivos médicos y protección radiológica."', NULL, NULL),
+ ('da561ec9-1156-46db-88cf-5d5a9e2f3fe7', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-27 15:34:16.6225+00', 'ACTUALIZACION_CAMPO', 'duracion_del_ciclo_escolar', '"16 semanas por ciclo"', NULL, NULL),
+ ('f1d9cfc3-12bb-4a9d-8b78-affc1ff793b6', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-27 15:34:16.6225+00', 'ACTUALIZACION_CAMPO', 'total_de_ciclos_del_plan_de_estudios', '"8"', NULL, NULL),
+ ('13779695-78e1-4f10-8ca2-16ee34b572c1', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-27 15:34:16.6225+00', 'ACTUALIZACION_CAMPO', 'fines_de_aprendizaje_o_formacion', '":[\"Analizar fundamentos de física médica, radiación, dosimetría y protección radiológica para su implementación clínica.\",\"Diseñar protocolos de adquisición de imágenes y dosimetría en entornos clínicos con criterios de calidad.\",\"Aplicar técnicas de imagen médica y radioterapia cumpliendo normativas de protección y seguridad.\",\"Evaluar equipos y procesos de calibración, mantenimiento y control de calidad en laboratorios y unidades clínicas.\",\"Comunicar resultados, hallazgos y recomendaciones técnicas a equipos médicos y pacientes con claridad y ética.\",\"Integrar enfoques de investigación y mejora continua para optimizar resultados en radiología y medicina física.\"]"', NULL, NULL),
+ ('8b8fb619-54b7-4fa1-bf85-b604bee00472', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-27 15:34:16.6225+00', 'ACTUALIZACION_CAMPO', 'nombre', '"Física Médica"', NULL, NULL),
+ ('58d5d433-3fdc-455b-ac8b-41dcb94ff52e', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-27 15:34:16.6225+00', 'ACTUALIZACION_CAMPO', 'clave_del_plan_de_estudios', '"2026-FM-01"', NULL, NULL),
+ ('aebfca6d-f418-4bca-86cd-c15398fb6f6c', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-27 15:34:16.6225+00', 'ACTUALIZACION_CAMPO', 'diseno_curricular', '"Flexible"', NULL, NULL),
+ ('8409cf58-4551-4282-8f50-e68466bc329e', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-27 15:34:16.6225+00', 'ACTUALIZACION_CAMPO', 'antecedente_academico', '"Bachillerato en Ciencias con énfasis en física y matemáticas; cursos de física general, química, cálculo diferencial e integral; promedio mínimo recomendado de ingreso 7.0; acreditar fundamentos de laboratorio y prácticas básicas cuando aplique."', NULL, NULL),
+ ('bcd7e8b2-87e8-4f73-a834-9a15cfea62b1', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-27 15:34:16.6225+00', 'ACTUALIZACION_CAMPO', 'modalidad_educativa', '"Mixta"', NULL, NULL),
+ ('5fd1577f-c430-4744-b541-dfa35b39bc6e', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-29 21:17:13.395925+00', 'ACTUALIZACION_CAMPO', 'ciclo', NULL, '{"description": "agregando campo nuevo con hook"}', NULL),
+ ('76e826fd-ac58-44f6-a28a-3cd5e2223ce0', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-29 21:19:10.511118+00', 'ACTUALIZACION_CAMPO', 'ciclo', '{"description": "agregando campo nuevo con hook"}', '{"description": "agregando nuevo campo a datos"}', NULL),
+ ('574ec330-0c3d-4b68-a078-369b6353e583', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-29 21:26:48.623104+00', 'ACTUALIZACION_CAMPO', 'ciclo', '{"description": "agregando nuevo campo a datos"}', '{"title": "Ciclo", "description": "agregando nuevo campo a datos"}', NULL),
+ ('e1fc6e45-a37f-45c1-9e37-12fc82d4960a', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-29 21:31:03.219219+00', 'ACTUALIZACION_CAMPO', 'ciclo', '{"title": "Ciclo", "description": "agregando nuevo campo a datos"}', '{"title": "Ciclo", "description": "hola"}', NULL),
+ ('bd739972-6d4e-44b6-a15a-9409adb87a14', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-29 21:40:29.585195+00', 'ACTUALIZACION_CAMPO', 'nombre_autorizado_de_la_institucion', NULL, '{"description": "guardar desde hook"}', NULL),
+ ('507284a0-9789-4058-98f3-d9f0176ca793', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-29 21:55:58.683479+00', 'ACTUALIZACION_CAMPO', 'nombre_autorizado_de_la_institucion', '{"description": "guardar desde hook"}', '{"description": "hola a todos"}', NULL),
+ ('c4262294-c67d-4c0c-816a-4d06d0b118be', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-29 21:57:05.346704+00', 'ACTUALIZACION_CAMPO', 'modalidad_educativa', '"Escolar"', '"Escolar2"', NULL),
+ ('4f6e8013-1626-414d-85b0-066587f09366', 'ddf2203b-2f55-4a32-9ee6-a99a6ff5947e', NULL, '2026-01-29 21:58:12.447523+00', 'ACTUALIZACION_CAMPO', 'nombre_autorizado_de_la_institucion', NULL, '"Agregando nuevos datos con persistencia"', NULL),
+ ('54c2e912-6845-4aa2-a052-9839c78f44a7', 'f6280a08-226d-480b-ab67-561e4657bfc4', NULL, '2026-01-30 14:33:51.01724+00', 'CREACION', NULL, NULL, '{"id": "f6280a08-226d-480b-ab67-561e4657bfc4", "datos": {}, "nivel": "Licenciatura", "activo": true, "nombre": "Ingeniería Económica y Financiera", "creado_en": "2026-01-30T14:33:50.818+00:00", "plan_hash": "ad41e1fcbf8ed0ba4c898086", "carrera_id": "b56eb209-245d-4258-a6a7-9dcb1d231cb4", "creado_por": null, "tipo_ciclo": "Semestre", "meta_origen": {}, "tipo_origen": "MANUAL", "estructura_id": "69fb2b77-5a95-47e0-bf1f-389d384200e4", "nombre_search": "ingenieria economica y financiera", "numero_ciclos": 9, "actualizado_en": "2026-01-30T14:33:50.818+00:00", "actualizado_por": null, "conversation_id": null, "estado_actual_id": "18f49b67-8077-4371-be6e-2019a3be3562"}', NULL),
+ ('a037db36-acbf-4243-a297-e819700a61c1', '79fba458-aea4-4ca8-ab76-943ede479f15', NULL, '2026-01-30 14:44:47.489255+00', 'CREACION', NULL, NULL, '{"id": "79fba458-aea4-4ca8-ab76-943ede479f15", "datos": {}, "nivel": "Licenciatura", "activo": true, "nombre": "prueba", "creado_en": "2026-01-30T14:44:48.312+00:00", "plan_hash": "a288feae19ba5ab74aca5fc0", "carrera_id": "b56eb209-245d-4258-a6a7-9dcb1d231cb4", "creado_por": null, "tipo_ciclo": "Semestre", "meta_origen": {}, "tipo_origen": "MANUAL", "estructura_id": "69fb2b77-5a95-47e0-bf1f-389d384200e4", "nombre_search": "prueba", "numero_ciclos": 9, "actualizado_en": "2026-01-30T14:44:48.312+00:00", "actualizado_por": null, "conversation_id": null, "estado_actual_id": "18f49b67-8077-4371-be6e-2019a3be3562"}', NULL),
+ ('15a2ee61-96fc-42e0-96f3-ab343dcb6538', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-30 16:59:49.3536+00', 'ACTUALIZACION_CAMPO', 'modalidad_educativa', '"Escolar2"', '"Escolar2222"', NULL),
+ ('7d4962c5-1ffa-4faf-928d-e96648ecba81', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-30 17:03:04.061333+00', 'ACTUALIZACION_CAMPO', 'nombre_autorizado_de_la_institucion', '{"description": "hola a todos"}', '{"description": "hola"}', NULL),
+ ('685bd46c-7302-411f-97dc-601e0d609641', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-30 17:09:00.477359+00', 'ACTUALIZACION_CAMPO', 'modalidad_educativa', '"Escolar2222"', '"Probando guardado"', NULL),
+ ('9030308b-fb15-409b-a969-1fc4d9b4b592', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-30 17:09:46.207083+00', 'ACTUALIZACION_CAMPO', 'nombre_autorizado_de_la_institucion', '{"description": "hola"}', '{"description": "lkdslkñdsksdñl"}', NULL),
+ ('18a1a5de-3395-4e9f-aaf3-d2fdb290f207', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-30 17:25:23.336995+00', 'ACTUALIZACION_CAMPO', 'ciclo', '{"title": "Ciclo", "description": "hola"}', NULL, NULL),
+ ('22fc0807-b98c-4664-a49c-f8224b3a6cc3', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-30 17:25:23.336995+00', 'ACTUALIZACION_CAMPO', 'nombre_autorizado_de_la_institucion', '{"description": "lkdslkñdsksdñl"}', NULL, NULL),
+ ('b98eb094-7ff5-41a5-98eb-ac85633a0045', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-30 17:25:23.336995+00', 'ACTUALIZACION_CAMPO', 'nombre_autorizado_de_la_inst', NULL, '"holis"', NULL),
+ ('5c423e8d-f3e0-42e9-94a1-71dea398e151', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-30 17:26:22.769183+00', 'ACTUALIZACION_CAMPO', 'nombre_autorizado_de_la_institucion', NULL, '"holis"', NULL),
+ ('3096ae88-1522-47d8-bc0e-61a255f37af0', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-30 17:26:22.769183+00', 'ACTUALIZACION_CAMPO', 'nombre_autorizado_de_la_inst', '"holis"', NULL, NULL),
+ ('297687c4-64a3-42fe-8eac-70ae555061e2', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-30 17:47:00.432194+00', 'ACTUALIZACION_CAMPO', 'nombre_autorizado_de_la_institucion', '"holis"', '"Probando persistencia\n"', NULL),
+ ('761bc69b-0a28-4840-8ac8-ed6b77621454', '6756a45c-e6b7-477b-918d-821ad7efbc58', NULL, '2026-01-30 18:31:10.882192+00', 'CREACION', NULL, NULL, '{"id": "6756a45c-e6b7-477b-918d-821ad7efbc58", "datos": {"vigencia": "2026-2031", "area_de_estudio": "Ingeniería y Tecnología", "perfil_de_egreso": "Conocimientos: fundamentos de mecánica, electrónica, control, robótica, sensores y actuadores; Habilidades: diseño y simulación de sistemas mecatrónicos, integración de hardware y software, gestión de proyectos y comunicación técnica; Actitudes: ética profesional, seguridad, trabajo en equipo, aprendizaje continuo y enfoque a la mejora continua.", "diseno_curricular": "Flexible", "perfil_de_ingreso": "Bachillerato en ciencias, tecnología o equivalente, con base sólida en matemáticas (álgebra, cálculo), física general y química; habilidades básicas de lectura técnica, razonamiento lógico y manejo de herramientas informáticas (al menos conceptos de programación y modelado).", "curso_propedeutico": null, "modalidad_educativa": "Mixta", "antecedente_academico": "Bachillerato en Ciencias y/o Tecnologías con promedio mínimo de 7.0; acceso preferente a carreras de ingeniería y tecnología; cumplimiento de requisitos de ingreso institucional.", "carga_horaria_a_la_semana": 28, "programa_de_investigacion": null, "clave_del_plan_de_estudios": "IM-2026-LIC-MEC", "duracion_del_ciclo_escolar": "16 semanas", "fines_de_aprendizaje_o_formacion": "Analizar problemas de ingeniería desde una visión integral de mecánica, electrónica y control; Diseñar sistemas mecatrónicos que integren hardware y software; Implementar modelos y simulaciones para validación de desempeño; Evaluar alternativas de diseño considerando seguridad, costo y sostenibilidad; Aplicar metodologías de gestión de proyectos para desarrollo de prototipos; Integrar tecnologías de manufactura avanzada y robótica en entornos industriales; Desarrollar habilidades de comunicación técnica y trabajo en equipo en contextos multidisciplinarios.", "nivel_y_nombre_del_plan_de_estudios": "Licenciatura en Ingeniería Mecatrónica", "nombre_autorizado_de_la_institucion": "Universidad La Salle México", "total_de_ciclos_del_plan_de_estudios": "9", "sustento_teorico_del_modelo_curricular": "El modelo curricular se fundamenta en un enfoque de ingeniería multidisciplinaria que integra principios de mecánica, electrónica, control, robótica y manufactura. Se sustenta en un continuum de aprendizaje basado en proyectos, ciencias de la ingeniería y prácticas de laboratorio, con énfasis en competencias profesionales y capacidad de adaptación tecnológica. Se alinea con el registro de planes de estudio vigente y con el marco de la CMPE 2016 para planes de estudio por campos de formación académica.", "justificacion_de_la_propuesta_curricular": "La modalidad Mixta exige flexibilidad para autogestión y aprendizaje autónomo, soportada por plataformas tecnológicas y recursos digitales. La propuesta aprovecha Moodle u otras LMS, laboratorios virtuales y contenidos asincrónicos para complementar la enseñanza presencial, permitiendo continuidad educativa ante variaciones geográficas o laborales de los estudiantes.", "administracion_y_operatividad_del_plan_de_estudios": "La administración del plan está a cargo de la Dirección de Ingeniería y la Coordinación de Planes de Estudio. Se asigna un Coordinador Académico por ciclo y se mantiene un registro central en el sistema de gestión institucional. Se establecen reglas para la aprobación de cambios curriculares, distribución de asignaturas por ciclo y seguimiento de requisitos de egreso. Se definen criterios de admisión, permanencia y titulación, con responsable de cumplimiento institucional.", "propuesta_de_evaluacion_periodica_del_plan_de_estudios": "Evaluación anual del plan mediante análisis de resultados de aprendizaje, desempeño de proyectos y prácticas; Instrumentos: rúbricas de cursos, guías de laboratorio, evaluaciones de conocimiento y competencias, y cuestionarios de satisfacción. Periodicidad: revisión anual con reporte a la instancia institucional y a SEP cuando corresponda; indicadores de calidad: tasa de retención, satisfacción estudiantil, empleabilidad y ajuste curricular por áreas de conocimiento.", "nombre_y_cargo_de_la_persona_facultada_para_autorizar_el_plan_de_estudios": "Lic. María González Pérez, Directora Académica"}, "nivel": "Licenciatura", "activo": true, "nombre": "Ingeniería Mecatrónica (2026)", "creado_en": "2026-01-30T18:31:10.882192+00:00", "plan_hash": "49aaec4fef4d574887fc6a60", "carrera_id": "089cdeda-d557-4a57-b49f-eb44921dfa3a", "creado_por": null, "tipo_ciclo": "Semestre", "meta_origen": {"iaConfig": {"usarMCP": false, "instruccionesAdicionalesIA": "", "descripcionEnfoqueAcademico": "Genera un plan de estudios sin rodeos."}, "referencias": {"repositoriosIds": [], "archivosReferenciaIds": []}, "generado_por": "ai_generate_plan", "ai_structured": {"model": "gpt-5-nano-2025-08-07", "usage": {"input_tokens": 3565, "total_tokens": 10864, "output_tokens": 7299, "input_tokens_details": {"cached_tokens": 0}, "output_tokens_details": {"reasoning_tokens": 6400}}, "responseId": "resp_07eb32291fa2232900697cf8a8e7f4819dbaab7110202db4bf", "conversationId": null}}, "tipo_origen": "IA", "estructura_id": "69fb2b77-5a95-47e0-bf1f-389d384200e4", "nombre_search": "ingenieria mecatronica (2026)", "numero_ciclos": 9, "actualizado_en": "2026-01-30T18:31:10.882192+00:00", "actualizado_por": null, "conversation_id": null, "estado_actual_id": "18f49b67-8077-4371-be6e-2019a3be3562"}', NULL),
+ ('41b1fb1c-7a8e-441a-b53c-d6f712bc97a5', '6756a45c-e6b7-477b-918d-821ad7efbc58', NULL, '2026-01-30 18:38:32.633716+00', 'OTRO', 'DELETE', '{"id": "6756a45c-e6b7-477b-918d-821ad7efbc58", "datos": {"vigencia": "2026-2031", "area_de_estudio": "Ingeniería y Tecnología", "perfil_de_egreso": "Conocimientos: fundamentos de mecánica, electrónica, control, robótica, sensores y actuadores; Habilidades: diseño y simulación de sistemas mecatrónicos, integración de hardware y software, gestión de proyectos y comunicación técnica; Actitudes: ética profesional, seguridad, trabajo en equipo, aprendizaje continuo y enfoque a la mejora continua.", "diseno_curricular": "Flexible", "perfil_de_ingreso": "Bachillerato en ciencias, tecnología o equivalente, con base sólida en matemáticas (álgebra, cálculo), física general y química; habilidades básicas de lectura técnica, razonamiento lógico y manejo de herramientas informáticas (al menos conceptos de programación y modelado).", "curso_propedeutico": null, "modalidad_educativa": "Mixta", "antecedente_academico": "Bachillerato en Ciencias y/o Tecnologías con promedio mínimo de 7.0; acceso preferente a carreras de ingeniería y tecnología; cumplimiento de requisitos de ingreso institucional.", "carga_horaria_a_la_semana": 28, "programa_de_investigacion": null, "clave_del_plan_de_estudios": "IM-2026-LIC-MEC", "duracion_del_ciclo_escolar": "16 semanas", "fines_de_aprendizaje_o_formacion": "Analizar problemas de ingeniería desde una visión integral de mecánica, electrónica y control; Diseñar sistemas mecatrónicos que integren hardware y software; Implementar modelos y simulaciones para validación de desempeño; Evaluar alternativas de diseño considerando seguridad, costo y sostenibilidad; Aplicar metodologías de gestión de proyectos para desarrollo de prototipos; Integrar tecnologías de manufactura avanzada y robótica en entornos industriales; Desarrollar habilidades de comunicación técnica y trabajo en equipo en contextos multidisciplinarios.", "nivel_y_nombre_del_plan_de_estudios": "Licenciatura en Ingeniería Mecatrónica", "nombre_autorizado_de_la_institucion": "Universidad La Salle México", "total_de_ciclos_del_plan_de_estudios": "9", "sustento_teorico_del_modelo_curricular": "El modelo curricular se fundamenta en un enfoque de ingeniería multidisciplinaria que integra principios de mecánica, electrónica, control, robótica y manufactura. Se sustenta en un continuum de aprendizaje basado en proyectos, ciencias de la ingeniería y prácticas de laboratorio, con énfasis en competencias profesionales y capacidad de adaptación tecnológica. Se alinea con el registro de planes de estudio vigente y con el marco de la CMPE 2016 para planes de estudio por campos de formación académica.", "justificacion_de_la_propuesta_curricular": "La modalidad Mixta exige flexibilidad para autogestión y aprendizaje autónomo, soportada por plataformas tecnológicas y recursos digitales. La propuesta aprovecha Moodle u otras LMS, laboratorios virtuales y contenidos asincrónicos para complementar la enseñanza presencial, permitiendo continuidad educativa ante variaciones geográficas o laborales de los estudiantes.", "administracion_y_operatividad_del_plan_de_estudios": "La administración del plan está a cargo de la Dirección de Ingeniería y la Coordinación de Planes de Estudio. Se asigna un Coordinador Académico por ciclo y se mantiene un registro central en el sistema de gestión institucional. Se establecen reglas para la aprobación de cambios curriculares, distribución de asignaturas por ciclo y seguimiento de requisitos de egreso. Se definen criterios de admisión, permanencia y titulación, con responsable de cumplimiento institucional.", "propuesta_de_evaluacion_periodica_del_plan_de_estudios": "Evaluación anual del plan mediante análisis de resultados de aprendizaje, desempeño de proyectos y prácticas; Instrumentos: rúbricas de cursos, guías de laboratorio, evaluaciones de conocimiento y competencias, y cuestionarios de satisfacción. Periodicidad: revisión anual con reporte a la instancia institucional y a SEP cuando corresponda; indicadores de calidad: tasa de retención, satisfacción estudiantil, empleabilidad y ajuste curricular por áreas de conocimiento.", "nombre_y_cargo_de_la_persona_facultada_para_autorizar_el_plan_de_estudios": "Lic. María González Pérez, Directora Académica"}, "nivel": "Licenciatura", "activo": true, "nombre": "Ingeniería Mecatrónica (2026)", "creado_en": "2026-01-30T18:31:10.882192+00:00", "plan_hash": "49aaec4fef4d574887fc6a60", "carrera_id": "089cdeda-d557-4a57-b49f-eb44921dfa3a", "creado_por": null, "tipo_ciclo": "Semestre", "meta_origen": {"iaConfig": {"usarMCP": false, "instruccionesAdicionalesIA": "", "descripcionEnfoqueAcademico": "Genera un plan de estudios sin rodeos."}, "referencias": {"repositoriosIds": [], "archivosReferenciaIds": []}, "generado_por": "ai_generate_plan", "ai_structured": {"model": "gpt-5-nano-2025-08-07", "usage": {"input_tokens": 3565, "total_tokens": 10864, "output_tokens": 7299, "input_tokens_details": {"cached_tokens": 0}, "output_tokens_details": {"reasoning_tokens": 6400}}, "responseId": "resp_07eb32291fa2232900697cf8a8e7f4819dbaab7110202db4bf", "conversationId": null}}, "tipo_origen": "IA", "estructura_id": "69fb2b77-5a95-47e0-bf1f-389d384200e4", "nombre_search": "ingenieria mecatronica (2026)", "numero_ciclos": 9, "actualizado_en": "2026-01-30T18:31:10.882192+00:00", "actualizado_por": null, "conversation_id": null, "estado_actual_id": "18f49b67-8077-4371-be6e-2019a3be3562"}', NULL, NULL),
+ ('39f3fc54-e2bf-4c8e-a886-82f186eda0bd', '0e0aea4d-b8b4-4e75-8279-6224c3ac769f', '11111111-1111-1111-1111-111111111111', '2026-01-30 20:18:43.823019+00', 'ACTUALIZACION_CAMPO', 'nombre_autorizado_de_la_institucion', '"Probando persistencia\n"', '"Agregando informacion persistente"', NULL),
+ ('84ff1732-b6ca-4fef-be82-ff7f0e0a4ce4', 'c5d80b6d-5d3d-4730-a4d5-8eaec3287910', NULL, '2026-01-30 21:03:51.728513+00', 'CREACION', NULL, NULL, '{"id": "c5d80b6d-5d3d-4730-a4d5-8eaec3287910", "datos": {}, "nivel": "Licenciatura", "activo": true, "nombre": "Derecho", "creado_en": "2026-01-30T21:03:52.036+00:00", "plan_hash": "697b32c2a0a186d1c66c9f6d", "carrera_id": "20593041-d605-4174-8404-4b152041eece", "creado_por": null, "tipo_ciclo": "Semestre", "meta_origen": {}, "tipo_origen": "MANUAL", "estructura_id": "69fb2b77-5a95-47e0-bf1f-389d384200e4", "nombre_search": "derecho", "numero_ciclos": 10, "actualizado_en": "2026-01-30T21:03:52.035+00:00", "actualizado_por": null, "conversation_id": null, "estado_actual_id": "18f49b67-8077-4371-be6e-2019a3be3562"}', NULL);
--
@@ -2107,7 +2258,11 @@ INSERT INTO "storage"."buckets" ("id", "name", "owner", "created_at", "updated_a
INSERT INTO "storage"."objects" ("id", "bucket_id", "name", "owner", "created_at", "updated_at", "last_accessed_at", "metadata", "version", "owner_id", "user_metadata", "level") VALUES
('c0813220-b62e-4503-be27-2f07cbc09914', 'plantillas', '.emptyFolderPlaceholder', NULL, '2026-01-08 20:03:05.299192+00', '2026-01-08 20:03:05.299192+00', '2026-01-08 20:03:05.299192+00', '{"eTag": "\"d41d8cd98f00b204e9800998ecf8427e\"", "size": 0, "mimetype": "application/octet-stream", "cacheControl": "max-age=3600", "lastModified": "2026-01-08T20:03:05.301Z", "contentLength": 0, "httpStatusCode": 200}', '29cb57d7-e0e2-4ee7-9637-a175084b694a', NULL, '{}', 1),
('923eeeab-dbdb-4de2-82af-2ee31fe5a985', 'ai-storage', 'tmp/95d1f381-7023-4f33-bc54-08ccd08abf41/701a1fdc-9dd6-4654-8c67-0aadcb8eadc2-carta.pdf', NULL, '2026-01-14 21:36:54.932547+00', '2026-01-14 21:36:54.932547+00', '2026-01-14 21:36:54.932547+00', '{"eTag": "\"f99de4473b8a614c53508a981284b605\"", "size": 38600, "mimetype": "application/pdf", "cacheControl": "max-age=3600", "lastModified": "2026-01-14T21:36:55.000Z", "contentLength": 38600, "httpStatusCode": 200}', '5bd1f251-6615-4520-a772-db3f993376a4', NULL, '{}', 3),
- ('145dd597-a7d1-4c6c-b446-0aa6b1c2e9d3', 'ai-storage', 'tmp/95d1f381-7023-4f33-bc54-08ccd08abf41/61d07fea-43fc-40d8-a43b-7852ea57d612-carta.pdf', NULL, '2026-01-15 20:15:47.957006+00', '2026-01-15 20:15:47.957006+00', '2026-01-15 20:15:47.957006+00', '{"eTag": "\"f99de4473b8a614c53508a981284b605\"", "size": 38600, "mimetype": "application/pdf", "cacheControl": "max-age=3600", "lastModified": "2026-01-15T20:15:48.000Z", "contentLength": 38600, "httpStatusCode": 200}', '4809478e-7c7f-4dd3-9f1c-a007ca7b5f90', NULL, '{}', 3);
+ ('145dd597-a7d1-4c6c-b446-0aa6b1c2e9d3', 'ai-storage', 'tmp/95d1f381-7023-4f33-bc54-08ccd08abf41/61d07fea-43fc-40d8-a43b-7852ea57d612-carta.pdf', NULL, '2026-01-15 20:15:47.957006+00', '2026-01-15 20:15:47.957006+00', '2026-01-15 20:15:47.957006+00', '{"eTag": "\"f99de4473b8a614c53508a981284b605\"", "size": 38600, "mimetype": "application/pdf", "cacheControl": "max-age=3600", "lastModified": "2026-01-15T20:15:48.000Z", "contentLength": 38600, "httpStatusCode": 200}', '4809478e-7c7f-4dd3-9f1c-a007ca7b5f90', NULL, '{}', 3),
+ ('192a4460-8334-4523-b232-f0e6c533d552', 'ai-storage', 'd932ce8a-b58f-4367-899c-22ca0f6974e0-Guia_para_el_sustentante_EGEL_Plus_IMECATRO_octubre_de_2025.pdf', NULL, '2026-01-22 21:43:02.023451+00', '2026-01-22 21:43:02.023451+00', '2026-01-22 21:43:02.023451+00', '{"eTag": "\"4d18550ebc9cd37c35c5bb92d8f29208\"", "size": 2426430, "mimetype": "application/pdf", "cacheControl": "max-age=3600", "lastModified": "2026-01-22T21:43:02.000Z", "contentLength": 2426430, "httpStatusCode": 200}', '31bfa884-2b06-437b-87e5-80c0cd90b804', NULL, '{}', 1),
+ ('4949d3bb-5063-473e-85d6-aa5fbee74026', 'ai-storage', 'cf64d8d5-ba79-4300-b346-5b566c894c79-Acuerdo_171117.pdf', NULL, '2026-01-22 21:43:02.525822+00', '2026-01-22 21:43:02.525822+00', '2026-01-22 21:43:02.525822+00', '{"eTag": "\"7efd81f7f78678b134ff2bcc4f44bce0\"", "size": 3852210, "mimetype": "application/pdf", "cacheControl": "max-age=3600", "lastModified": "2026-01-22T21:43:03.000Z", "contentLength": 3852210, "httpStatusCode": 200}', 'a714a24a-8cd5-4e04-9920-2598629b6d9f', NULL, '{}', 1),
+ ('22f2e7ce-cdf8-4325-8c51-91f494cdf01f', 'ai-storage', 'd36b3616-d686-4125-8719-d2160d2b37d0-file-sample_150kB.pdf', NULL, '2026-01-23 17:48:53.729633+00', '2026-01-23 17:48:53.729633+00', '2026-01-23 17:48:53.729633+00', '{"eTag": "\"5afaf79789a776d81ec91ccbdc9fdaba\"", "size": 142786, "mimetype": "application/pdf", "cacheControl": "max-age=3600", "lastModified": "2026-01-23T17:48:54.000Z", "contentLength": 142786, "httpStatusCode": 200}', '4badfeaa-422a-4905-9547-b46b700e8aa8', NULL, '{}', 1),
+ ('7cd652b2-8e19-4925-959e-785fb0ce29a3', 'ai-storage', 'a9cbe04d-b5ab-4917-846d-4f1e8745c95c-Plan_Genera_un_plan_de_estudios_claro_y_realista__De_fisica_cuantica_relacionada_con_blucles_Institucional.pdf', NULL, '2026-01-23 17:48:53.865584+00', '2026-01-23 17:48:53.865584+00', '2026-01-23 17:48:53.865584+00', '{"eTag": "\"2f33192d3446c4924424de3276b46c1f\"", "size": 40523, "mimetype": "application/pdf", "cacheControl": "max-age=3600", "lastModified": "2026-01-23T17:48:54.000Z", "contentLength": 40523, "httpStatusCode": 200}', '37e5691d-16c4-4392-b316-8aeecfda77e2', NULL, '{}', 1);
--
diff --git a/supabase/diffs/sql_para_local_como_remoto.sql b/supabase/diffs/sql_para_local_como_remoto.sql
new file mode 100644
index 0000000..4ddfd04
--- /dev/null
+++ b/supabase/diffs/sql_para_local_como_remoto.sql
@@ -0,0 +1,205 @@
+create extension if not exists "http" with schema "extensions";
+
+alter table "public"."asignaturas" drop constraint "asignaturas_facultad_propietaria_id_fkey";
+
+alter table "public"."cambios_plan" drop constraint "cambios_plan_plan_estudio_id_fkey";
+
+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', 'ACTUALIZACION');
+
+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";
+
+alter table "public"."asignaturas" drop column "facultad_propietaria_id";
+
+alter table "public"."asignaturas" add column "asignatura_hash" text generated always as (encode(SUBSTRING(extensions.digest((id)::text, 'sha512'::text) FROM 1 FOR 12), 'hex'::text)) stored;
+
+alter table "public"."cambios_plan" drop column "interaccion_ia_id";
+
+alter table "public"."cambios_plan" add column "response_id" text;
+
+alter table "public"."planes_estudio" add column "conversation_id" text;
+
+alter table "public"."planes_estudio" add column "plan_hash" text generated always as (encode(SUBSTRING(extensions.digest((id)::text, 'sha512'::text) FROM 1 FOR 12), 'hex'::text)) stored;
+
+CREATE UNIQUE INDEX planes_estudio_conversation_id_key ON public.planes_estudio USING btree (conversation_id);
+
+alter table "public"."planes_estudio" add constraint "planes_estudio_conversation_id_key" UNIQUE using index "planes_estudio_conversation_id_key";
+
+set check_function_bodies = off;
+
+CREATE OR REPLACE FUNCTION public.fn_log_cambios_planes_estudio()
+ RETURNS trigger
+ LANGUAGE plpgsql
+AS $function$declare
+ k text;
+ old_val jsonb;
+ new_val jsonb;
+
+ v_response_id text;
+begin
+ v_response_id := nullif(new.meta_origen->>'response_id','');
+
+ -- INSERT -> CREACION
+ if tg_op = 'INSERT' then
+ insert into public.cambios_plan (
+ plan_estudio_id,
+ cambiado_por,
+ tipo,
+ campo,
+ valor_anterior,
+ valor_nuevo,
+ response_id
+ )
+ values (
+ new.id,
+ new.creado_por,
+ 'CREACION'::public.tipo_cambio,
+ null,
+ null,
+ to_jsonb(new),
+ null
+ );
+
+ return new;
+ end if;
+
+ -- DELETE (opcional): si no lo quieres, bórralo
+ if tg_op = 'DELETE' then
+ insert into public.cambios_plan (
+ plan_estudio_id,
+ cambiado_por,
+ tipo,
+ campo,
+ valor_anterior,
+ valor_nuevo,
+ response_id
+ )
+ values (
+ old.id,
+ old.actualizado_por,
+ 'OTRO'::public.tipo_cambio,
+ 'DELETE',
+ to_jsonb(old),
+ null,
+ null
+ );
+
+ return old;
+ end if;
+
+ -- UPDATE ----------------------------------------------------------
+ -- 1) Transición de estado
+ if (new.estado_actual_id is distinct from old.estado_actual_id) then
+ insert into public.cambios_plan (
+ plan_estudio_id, cambiado_por, tipo, campo, valor_anterior, valor_nuevo, response_id
+ )
+ values (
+ new.id,
+ new.actualizado_por,
+ 'TRANSICION_ESTADO'::public.tipo_cambio,
+ 'estado_actual_id',
+ to_jsonb(old.estado_actual_id),
+ to_jsonb(new.estado_actual_id),
+ null
+ );
+ end if;
+
+ -- 2) Cambios en JSONB "datos" (diff top-level por llave)
+ if (new.datos is distinct from old.datos) then
+ for k in
+ select distinct key
+ from (
+ select jsonb_object_keys(coalesce(old.datos, '{}'::jsonb)) as key
+ union all
+ select jsonb_object_keys(coalesce(new.datos, '{}'::jsonb)) as key
+ ) t
+ loop
+ old_val := coalesce(old.datos, '{}'::jsonb) -> k;
+ new_val := coalesce(new.datos, '{}'::jsonb) -> k;
+
+ if (old_val is distinct from new_val) then
+ insert into public.cambios_plan (
+ plan_estudio_id, cambiado_por, tipo, campo, valor_anterior, valor_nuevo, response_id
+ )
+ values (
+ new.id,
+ new.actualizado_por,
+ 'ACTUALIZACION_CAMPO'::public.tipo_cambio,
+ k,
+ old_val,
+ new_val,
+ v_response_id
+ );
+ end if;
+ end loop;
+
+ end if;
+
+ -- 3) Cambios en columnas "normales" (uno por columna)
+ if (new.nombre is distinct from old.nombre) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'nombre', to_jsonb(old.nombre), to_jsonb(new.nombre), null);
+ end if;
+
+ if (new.nivel is distinct from old.nivel) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'nivel', to_jsonb(old.nivel), to_jsonb(new.nivel), null);
+ end if;
+
+ if (new.tipo_ciclo is distinct from old.tipo_ciclo) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'tipo_ciclo', to_jsonb(old.tipo_ciclo), to_jsonb(new.tipo_ciclo), null);
+ end if;
+
+ if (new.numero_ciclos is distinct from old.numero_ciclos) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'numero_ciclos', to_jsonb(old.numero_ciclos), to_jsonb(new.numero_ciclos), null);
+ end if;
+
+ if (new.activo is distinct from old.activo) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'activo', to_jsonb(old.activo), to_jsonb(new.activo), null);
+ end if;
+
+ if (new.carrera_id is distinct from old.carrera_id) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'carrera_id', to_jsonb(old.carrera_id), to_jsonb(new.carrera_id), null);
+ end if;
+
+ if (new.estructura_id is distinct from old.estructura_id) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'estructura_id', to_jsonb(old.estructura_id), to_jsonb(new.estructura_id), null);
+ end if;
+
+ if (new.tipo_origen is distinct from old.tipo_origen) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'tipo_origen', to_jsonb(old.tipo_origen), to_jsonb(new.tipo_origen), null);
+ end if;
+
+
+ if (new.conversation_id is distinct from old.conversation_id) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'conversation_id', to_jsonb(old.conversation_id), to_jsonb(new.conversation_id), null);
+ end if;
+
+ -- 🔥 consumirlo para que NO se guarde en planes_estudio
+ if v_response_id is not null then
+ new.meta_origen := new.meta_origen - 'response_id';
+ end if;
+
+ return new;
+end;$function$
+;
+
+CREATE TRIGGER "agregar-conversation_id-asignaturas" AFTER INSERT ON public.asignaturas FOR EACH ROW EXECUTE FUNCTION supabase_functions.http_request('https://exdkssurzmjnnhgtiama.supabase.co/functions/v1/create-chat-conversation', 'POST', '{"Content-type":"application/json","Authorization":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImV4ZGtzc3Vyem1qbm5oZ3RpYW1hIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc0MTM3ODYzMiwiZXhwIjoyMDU2OTU0NjMyfQ.s-GHuwnYbIYoMZN9dFbAKgxNyAtQllRCRPLy-GIRaro"}', '{}', '5000');
+
+CREATE TRIGGER "agregar-conversation_id-planes_estudio" AFTER INSERT ON public.planes_estudio FOR EACH ROW EXECUTE FUNCTION supabase_functions.http_request('https://exdkssurzmjnnhgtiama.supabase.co/functions/v1/create-chat-conversation', 'POST', '{"Content-type":"application/json","Authorization":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImV4ZGtzc3Vyem1qbm5oZ3RpYW1hIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc0MTM3ODYzMiwiZXhwIjoyMDU2OTU0NjMyfQ.s-GHuwnYbIYoMZN9dFbAKgxNyAtQllRCRPLy-GIRaro"}', '{}', '5000');
+
+CREATE TRIGGER trg_planes_estudio_log_cambios AFTER INSERT OR DELETE OR UPDATE ON public.planes_estudio FOR EACH ROW EXECUTE FUNCTION public.fn_log_cambios_planes_estudio();
+
+
diff --git a/supabase/diffs/sql_para_remoto_como_local.sql b/supabase/diffs/sql_para_remoto_como_local.sql
deleted file mode 100644
index f17c71e..0000000
--- a/supabase/diffs/sql_para_remoto_como_local.sql
+++ /dev/null
@@ -1,56 +0,0 @@
-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/_shared/openai-service.ts b/supabase/functions/_shared/openai-service.ts
index 0f46ff6..543c2a0 100644
--- a/supabase/functions/_shared/openai-service.ts
+++ b/supabase/functions/_shared/openai-service.ts
@@ -91,6 +91,30 @@ export class OpenAIService {
const uploadedToStorage = await this.uploadFilesToStorage(files ?? []);
const openaiFileIds = await this.uploadFilesToOpenAI(files ?? []);
+ const newOptions = { ...options };
+
+ // Attach file references to the request as an extra user message
+ if (openaiFileIds.length > 0) {
+ const fileParts: OpenAITypes.OpenAI.Responses.ResponseInputFile[] =
+ openaiFileIds.map((id) => ({
+ type: "input_file",
+ file_id: id,
+ }));
+
+ const arr = Array.isArray(options.input) ? options.input : [];
+ arr.push({
+ role: "user",
+ content: [
+ ...fileParts,
+ {
+ type: "input_text",
+ text: "Usa estos archivos como referencia",
+ },
+ ],
+ });
+ newOptions.input = arr;
+ }
+
// Narrow to non-streaming response
const openaiRaw = (await this.openai.responses.create(
newOptions as OpenAITypes.OpenAI.Responses.ResponseCreateParamsNonStreaming,
@@ -98,12 +122,11 @@ export class OpenAIService {
const { model, id: responseId } = openaiRaw;
const usage = openaiRaw?.usage ?? null;
- const conversationId =
- (
- openaiRaw as OpenAITypes.OpenAI.Responses.Response & {
- conversation_id?: string | null;
- }
- ).conversation_id ?? null;
+ const conversationId = (
+ openaiRaw as OpenAITypes.OpenAI.Responses.Response & {
+ conversation_id?: string | null;
+ }
+ ).conversation_id ?? null;
// Try to read structured JSON output
let output: TOutput | undefined = undefined;
@@ -148,8 +171,8 @@ export class OpenAIService {
const code = message.includes("Supabase upload failed")
? "StorageUploadFailed"
: message.includes("OpenAI file upload failed")
- ? "OpenAIFileUploadFailed"
- : "OpenAIRequestFailed";
+ ? "OpenAIFileUploadFailed"
+ : "OpenAIRequestFailed";
return { ok: false, code, message, cause: err };
}
diff --git a/supabase/functions/_shared/utils.ts b/supabase/functions/_shared/utils.ts
new file mode 100644
index 0000000..7516709
--- /dev/null
+++ b/supabase/functions/_shared/utils.ts
@@ -0,0 +1,49 @@
+import { corsHeaders } from "./cors.ts";
+
+// --- CLASE DE ERROR ---
+export class HttpError extends Error {
+ public readonly status: number;
+ public readonly code: string;
+ public readonly internalDetails: unknown;
+
+ constructor(
+ status: number,
+ message: string,
+ code = "API_ERROR",
+ internalDetails?: unknown,
+ ) {
+ super(message);
+ this.name = "HttpError";
+ this.status = status;
+ this.code = code;
+ this.internalDetails = internalDetails;
+ }
+}
+
+// --- HELPER DE ÉXITO ---
+export function sendSuccess(data: T, status = 200): Response {
+ return new Response(
+ JSON.stringify(data),
+ {
+ status,
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
+ },
+ );
+}
+
+// --- HELPER DE ERROR ---
+export function sendError(
+ status: number,
+ message: string,
+ code: string,
+): Response {
+ return new Response(
+ JSON.stringify({
+ error: { message, code },
+ }),
+ {
+ status,
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
+ },
+ );
+}
diff --git a/supabase/functions/ai-generate-plan/deno.json b/supabase/functions/ai-generate-plan/deno.json
index f5db280..8b204ca 100644
--- a/supabase/functions/ai-generate-plan/deno.json
+++ b/supabase/functions/ai-generate-plan/deno.json
@@ -1,5 +1,8 @@
{
"imports": {
- "zod": "https://deno.land/x/zod@v3.22.4/mod.ts"
+ "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-plan/index.ts b/supabase/functions/ai-generate-plan/index.ts
index f4e61c1..0f3185b 100644
--- a/supabase/functions/ai-generate-plan/index.ts
+++ b/supabase/functions/ai-generate-plan/index.ts
@@ -5,6 +5,7 @@
// Setup type definitions for built-in Supabase Runtime APIs
import "jsr:@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 type { Database, Json } from "../_shared/database.types.ts";
import type { AIGeneratePlanInput } from "./types.ts";
@@ -18,7 +19,7 @@ type NivelType =
type TipoCicloType =
Database["public"]["Tables"]["planes_estudio"]["Insert"]["tipo_ciclo"];
-Deno.serve(async (req) => {
+Deno.serve(async (req: Request): Promise => {
const url = new URL(req.url);
const functionName = url.pathname.split("/").pop();
console.log(
@@ -37,12 +38,11 @@ Deno.serve(async (req) => {
new Date().toISOString()
}][${functionName}]: Invalid method: ${method}`,
);
- return new Response(
- JSON.stringify({ error: "Method not allowed" }),
- {
- headers: { ...corsHeaders, "Content-Type": "application/json" },
- status: 405,
- },
+ throw new HttpError(
+ 405,
+ "Método no permitido.",
+ "METHOD_NOT_ALLOWED",
+ { method },
);
}
@@ -54,12 +54,11 @@ Deno.serve(async (req) => {
new Date().toISOString()
}][${functionName}]: Missing Authorization header`,
);
- return new Response(
- JSON.stringify({ error: "Authorization header missing" }),
- {
- headers: { ...corsHeaders, "Content-Type": "application/json" },
- status: 401,
- },
+ throw new HttpError(
+ 401,
+ "No autorizado.",
+ "UNAUTHORIZED",
+ { reason: "missing_authorization_header" },
);
}
@@ -70,19 +69,28 @@ Deno.serve(async (req) => {
new Date().toISOString()
}][${functionName}]: Unsupported content type: ${contentType}`,
);
- return new Response(
- JSON.stringify({ error: "Unsupported content type" }),
- {
- headers: { ...corsHeaders, "Content-Type": "application/json" },
- status: 400,
- },
+ 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) {
- throw new Error("Supabase environment variables are not set");
+ throw new HttpError(
+ 500,
+ "Configuración del servidor incompleta.",
+ "MISSING_ENV",
+ {
+ missing: [
+ !SUPABASE_URL ? "SUPABASE_URL" : null,
+ !SUPABASE_ANON_KEY ? "SUPABASE_ANON_KEY" : null,
+ ].filter(Boolean),
+ },
+ );
}
// If needed for RLS-protected reads, create an anon client with user's JWT
@@ -97,7 +105,12 @@ Deno.serve(async (req) => {
const SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY");
if (!SERVICE_ROLE_KEY) {
- throw new Error("SUPABASE_SERVICE_ROLE_KEY is not set");
+ throw new HttpError(
+ 500,
+ "Configuración del servidor incompleta.",
+ "MISSING_ENV",
+ { missing: ["SUPABASE_SERVICE_ROLE_KEY"] },
+ );
}
const supabaseService = createClient(
@@ -112,12 +125,15 @@ Deno.serve(async (req) => {
`[${new Date().toISOString()}][${functionName}]: Validation errors:`,
validation.errors,
);
- return new Response(
- JSON.stringify({ errors: validation.errors }),
- {
- headers: { ...corsHeaders, "Content-Type": "application/json" },
- status: 400,
- },
+ const message = validation.errors
+ .map((e, i) => `(${i + 1}) ${e}`)
+ .join("\n");
+
+ throw new HttpError(
+ 422,
+ message,
+ "VALIDATION_ERROR",
+ { errors: validation.errors },
);
}
@@ -130,8 +146,23 @@ Deno.serve(async (req) => {
.eq("id", payload.datosBasicos.estructuraPlanId)
.single();
if (estructuraPlanError) {
- throw new Error(
- "Error fetching estructuraPlan: " + estructuraPlanError.message,
+ const maybeCode = (estructuraPlanError as { code?: string }).code;
+ if (maybeCode === "PGRST116") {
+ throw new HttpError(
+ 404,
+ "No se encontró la estructura del plan.",
+ "NOT_FOUND",
+ {
+ table: "estructuras_plan",
+ id: payload.datosBasicos.estructuraPlanId,
+ },
+ );
+ }
+ throw new HttpError(
+ 500,
+ "No se pudo obtener la estructura del plan.",
+ "SUPABASE_QUERY_FAILED",
+ estructuraPlanError,
);
}
@@ -172,25 +203,64 @@ Deno.serve(async (req) => {
// Use shared OpenAI service directly (no HTTP invoke)
const svc = OpenAIService.fromEnv();
if (!(svc instanceof OpenAIService)) {
- throw new Error(`OpenAI service misconfiguration: ${svc.message}`);
+ throw new HttpError(
+ 500,
+ "Configuración del servidor incompleta.",
+ "OPENAI_MISCONFIGURED",
+ svc,
+ );
}
+ // INICIO DE CÓDIGO PARA DEBBUGGING
+ // console.log(
+ // `[${
+ // new Date().toISOString()
+ // }][${functionName}]: Request processed successfully`,
+ // );
+ // const { data: plan_debug } = await supabaseService
+ // .from("planes_estudio")
+ // .select("*")
+ // .eq("id", "7ce657b1-1abf-4972-858d-5fffe1d51499")
+ // .maybeSingle();
+ // return sendSuccess(plan_debug);
+ // FIN DE CÓDIGO PARA DEBBUGGING
+
const aiResult = await svc.createStructuredResponse(
aiStructuredPayload,
payload.archivosAdjuntos,
);
if (!aiResult.ok) {
- throw new Error(
- `OpenAI call failed [${aiResult.code}]: ${aiResult.message}`,
+ const status = aiResult.code === "MissingEnv" ? 500 : 502;
+ throw new HttpError(
+ status,
+ "No se pudo generar el plan con IA.",
+ "OPENAI_REQUEST_FAILED",
+ aiResult,
);
}
// Prefer parsed output; fallback to parse outputText
- const aiOutput = aiResult.output ??
- (aiResult.outputText ? JSON.parse(aiResult.outputText) : null);
+ 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 },
+ );
+ }
+ }
const aiOutputJson: Json = aiOutput as unknown as Json;
if (!aiOutput) {
- throw new Error("OpenAI response did not contain structured output");
+ throw new HttpError(
+ 502,
+ "La respuesta de la IA no contiene salida estructurada.",
+ "OPENAI_MISSING_STRUCTURED_OUTPUT",
+ { outputText: aiResult.outputText ?? null },
+ );
}
// TODO: Insertar interacciones con IA y quizas forzar a que la informacion de datosBasicos sea la misma que la recibida
@@ -209,7 +279,20 @@ Deno.serve(async (req) => {
.eq("id", payload.datosBasicos.carreraId)
.maybeSingle();
if (carreraError) {
- throw new Error("Error fetching carrera: " + carreraError.message);
+ throw new HttpError(
+ 500,
+ "No se pudo obtener la carrera.",
+ "SUPABASE_QUERY_FAILED",
+ carreraError,
+ );
+ }
+ if (!carrera) {
+ throw new HttpError(
+ 404,
+ "No se encontró la carrera.",
+ "NOT_FOUND",
+ { table: "carreras", id: payload.datosBasicos.carreraId },
+ );
}
const planInsert: Database["public"]["Tables"]["planes_estudio"]["Insert"] =
@@ -257,42 +340,62 @@ Deno.serve(async (req) => {
// TODO: interaccion con IA y cambio de estado
if (planError) {
- throw new Error("Error inserting plan: " + planError.message);
+ const maybeCode = (planError as { code?: string }).code;
+ // Common cases:
+ // - foreign key / constraint violations -> 409
+ // - others -> 500
+ const status = maybeCode ? 409 : 500;
+ throw new HttpError(
+ status,
+ "No se pudo guardar el plan de estudios.",
+ "SUPABASE_INSERT_FAILED",
+ { ...planError, code: maybeCode },
+ );
}
// TODO: update a interaccion_ia y e insert a cambios_plan con id de plan generado
- const jsonResponse = {
- ok: true,
- plan,
- };
console.log(
`[${
new Date().toISOString()
}][${functionName}]: Request processed successfully`,
);
- return new Response(
- JSON.stringify(jsonResponse),
- {
- headers: { ...corsHeaders, "Content-Type": "application/json" },
- status: 200,
- },
- );
+ return sendSuccess(plan);
} catch (error) {
- // Log full error server-side for diagnostics
- if (error instanceof Error) {
+ if (error instanceof HttpError) {
console.error(
- `[${
- new Date().toISOString()
- }][${functionName}]: Request handler error:`,
- error.message,
+ `[${new Date().toISOString()}][${functionName}] ⚠️ Handled Error:`,
+ {
+ message: error.message,
+ code: error.code,
+ internalDetails: error.internalDetails || "N/A",
+ },
);
+
+ // RESPONSE: Solo enviamos el mensaje limpio y el código
+ return sendError(error.status, error.message, error.code);
}
- const message = error instanceof Error ? error.message : String(error);
- return new Response(JSON.stringify({ error: message }), {
- headers: { ...corsHeaders, "Content-Type": "application/json" },
- status: 400,
- });
+
+ // CASO B: Error Inesperado (Crash, Bug, Syntax Error, etc.)
+ // El usuario NO debe ver esto.
+ const unexpectedError = error instanceof Error
+ ? error
+ : new Error(String(error));
+
+ // LOG: Full stack trace y mensaje real
+ console.error(
+ `[${
+ new Date().toISOString()
+ }][${functionName}] 💥 CRITICAL UNHANDLED ERROR:`,
+ unexpectedError.stack || unexpectedError.message, // Esto es lo que necesitas para debuguear
+ );
+
+ // RESPONSE: Mensaje genérico y seguro
+ return sendError(
+ 500,
+ "Ocurrió un error inesperado en el servidor.",
+ "INTERNAL_SERVER_ERROR",
+ );
}
});
@@ -324,10 +427,7 @@ const DatosBasicosSchema: z.ZodType = z
});
const IAConfigSchema: z.ZodType = z.object({
- descripcionEnfoqueAcademico: z.string().min(
- 10,
- "La descripción es muy corta",
- ),
+ descripcionEnfoqueAcademico: z.string(),
instruccionesAdicionalesIA: z.string().optional(),
archivosReferencia: z.array(z.string().uuid()).optional(),
repositoriosIds: z.array(z.string().uuid()).optional(),
diff --git a/supabase/functions/ai-generate-subject/.npmrc b/supabase/functions/ai-generate-subject/.npmrc
new file mode 100644
index 0000000..48c6388
--- /dev/null
+++ b/supabase/functions/ai-generate-subject/.npmrc
@@ -0,0 +1,3 @@
+# Configuration for private npm package dependencies
+# For more information on using private registries with Edge Functions, see:
+# https://supabase.com/docs/guides/functions/import-maps#importing-from-private-registries
diff --git a/supabase/functions/ai-generate-subject/deno.json b/supabase/functions/ai-generate-subject/deno.json
new file mode 100644
index 0000000..f6ca845
--- /dev/null
+++ b/supabase/functions/ai-generate-subject/deno.json
@@ -0,0 +1,3 @@
+{
+ "imports": {}
+}
diff --git a/supabase/functions/ai-generate-subject/index.ts b/supabase/functions/ai-generate-subject/index.ts
new file mode 100644
index 0000000..8cb545f
--- /dev/null
+++ b/supabase/functions/ai-generate-subject/index.ts
@@ -0,0 +1,25 @@
+import "jsr:@supabase/functions-js/edge-runtime.d.ts";
+import { corsHeaders } from "../_shared/cors.ts";
+
+console.log("Hello from Functions!");
+
+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 { name } = await req.json();
+ const data = {
+ message: `Hello ${name}!`,
+ };
+
+ return new Response(
+ JSON.stringify(data),
+ { headers: { "Content-Type": "application/json" } },
+ );
+});
diff --git a/supabase/functions/tests/ai-structured-test.ts b/supabase/functions/tests/ai-structured-test.ts
index 5a52e1e..0145f7d 100644
--- a/supabase/functions/tests/ai-structured-test.ts
+++ b/supabase/functions/tests/ai-structured-test.ts
@@ -25,9 +25,6 @@ function mustEnv() {
if (!SUPABASE_ANON_KEY) throw new Error("SUPABASE_ANON_KEY is required");
}
-async function getAuthedClient(): Promise<
- { client: SupabaseClient; accessToken: string }
-> {
async function getAuthedClient(): Promise<
{ client: SupabaseClient; accessToken: string }
> {
diff --git a/supabase/migrations/20260130215440_cambios_asignatura.sql b/supabase/migrations/20260130215440_cambios_asignatura.sql
new file mode 100644
index 0000000..cbf5fee
--- /dev/null
+++ b/supabase/migrations/20260130215440_cambios_asignatura.sql
@@ -0,0 +1,21 @@
+-- alter table "public"."asignaturas" drop constraint "asignaturas_facultad_propietaria_id_fkey";
+
+-- alter table "public"."asignaturas" drop constraint "asignaturas_horas_semana_check";
+
+-- alter table "public"."asignaturas" drop column "facultad_propietaria_id";
+
+alter table "public"."asignaturas" drop column "horas_semana";
+
+alter table "public"."asignaturas" add column "horas_academicas" integer;
+
+alter table "public"."asignaturas" add column "horas_independientes" integer;
+
+alter table "public"."asignaturas" add constraint "asignaturas_horas_academicas_check" CHECK (((horas_academicas IS NULL) OR (horas_academicas >= 0))) not valid;
+
+alter table "public"."asignaturas" validate constraint "asignaturas_horas_academicas_check";
+
+alter table "public"."asignaturas" add constraint "asignaturas_horas_independientes_check" CHECK (((horas_independientes IS NULL) OR (horas_independientes >= 0))) not valid;
+
+alter table "public"."asignaturas" validate constraint "asignaturas_horas_independientes_check";
+
+
diff --git a/supabase/migrations/20260203204213_sincronizacion_con_remoto.sql b/supabase/migrations/20260203204213_sincronizacion_con_remoto.sql
new file mode 100644
index 0000000..133a53f
--- /dev/null
+++ b/supabase/migrations/20260203204213_sincronizacion_con_remoto.sql
@@ -0,0 +1,205 @@
+create extension if not exists "http" with schema "extensions";
+
+alter table "public"."asignaturas" drop constraint if exists "asignaturas_facultad_propietaria_id_fkey";
+
+alter table "public"."cambios_plan" drop constraint "cambios_plan_plan_estudio_id_fkey";
+
+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', 'ACTUALIZACION');
+
+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";
+
+alter table "public"."asignaturas" drop column if exists "facultad_propietaria_id";
+
+alter table "public"."asignaturas" add column "asignatura_hash" text generated always as (encode(SUBSTRING(extensions.digest((id)::text, 'sha512'::text) FROM 1 FOR 12), 'hex'::text)) stored;
+
+alter table "public"."cambios_plan" drop column "interaccion_ia_id";
+
+alter table "public"."cambios_plan" add column "response_id" text;
+
+alter table "public"."planes_estudio" add column "conversation_id" text;
+
+alter table "public"."planes_estudio" add column "plan_hash" text generated always as (encode(SUBSTRING(extensions.digest((id)::text, 'sha512'::text) FROM 1 FOR 12), 'hex'::text)) stored;
+
+CREATE UNIQUE INDEX planes_estudio_conversation_id_key ON public.planes_estudio USING btree (conversation_id);
+
+alter table "public"."planes_estudio" add constraint "planes_estudio_conversation_id_key" UNIQUE using index "planes_estudio_conversation_id_key";
+
+set check_function_bodies = off;
+
+CREATE OR REPLACE FUNCTION public.fn_log_cambios_planes_estudio()
+ RETURNS trigger
+ LANGUAGE plpgsql
+AS $function$declare
+ k text;
+ old_val jsonb;
+ new_val jsonb;
+
+ v_response_id text;
+begin
+ v_response_id := nullif(new.meta_origen->>'response_id','');
+
+ -- INSERT -> CREACION
+ if tg_op = 'INSERT' then
+ insert into public.cambios_plan (
+ plan_estudio_id,
+ cambiado_por,
+ tipo,
+ campo,
+ valor_anterior,
+ valor_nuevo,
+ response_id
+ )
+ values (
+ new.id,
+ new.creado_por,
+ 'CREACION'::public.tipo_cambio,
+ null,
+ null,
+ to_jsonb(new),
+ null
+ );
+
+ return new;
+ end if;
+
+ -- DELETE (opcional): si no lo quieres, bórralo
+ if tg_op = 'DELETE' then
+ insert into public.cambios_plan (
+ plan_estudio_id,
+ cambiado_por,
+ tipo,
+ campo,
+ valor_anterior,
+ valor_nuevo,
+ response_id
+ )
+ values (
+ old.id,
+ old.actualizado_por,
+ 'OTRO'::public.tipo_cambio,
+ 'DELETE',
+ to_jsonb(old),
+ null,
+ null
+ );
+
+ return old;
+ end if;
+
+ -- UPDATE ----------------------------------------------------------
+ -- 1) Transición de estado
+ if (new.estado_actual_id is distinct from old.estado_actual_id) then
+ insert into public.cambios_plan (
+ plan_estudio_id, cambiado_por, tipo, campo, valor_anterior, valor_nuevo, response_id
+ )
+ values (
+ new.id,
+ new.actualizado_por,
+ 'TRANSICION_ESTADO'::public.tipo_cambio,
+ 'estado_actual_id',
+ to_jsonb(old.estado_actual_id),
+ to_jsonb(new.estado_actual_id),
+ null
+ );
+ end if;
+
+ -- 2) Cambios en JSONB "datos" (diff top-level por llave)
+ if (new.datos is distinct from old.datos) then
+ for k in
+ select distinct key
+ from (
+ select jsonb_object_keys(coalesce(old.datos, '{}'::jsonb)) as key
+ union all
+ select jsonb_object_keys(coalesce(new.datos, '{}'::jsonb)) as key
+ ) t
+ loop
+ old_val := coalesce(old.datos, '{}'::jsonb) -> k;
+ new_val := coalesce(new.datos, '{}'::jsonb) -> k;
+
+ if (old_val is distinct from new_val) then
+ insert into public.cambios_plan (
+ plan_estudio_id, cambiado_por, tipo, campo, valor_anterior, valor_nuevo, response_id
+ )
+ values (
+ new.id,
+ new.actualizado_por,
+ 'ACTUALIZACION_CAMPO'::public.tipo_cambio,
+ k,
+ old_val,
+ new_val,
+ v_response_id
+ );
+ end if;
+ end loop;
+
+ end if;
+
+ -- 3) Cambios en columnas "normales" (uno por columna)
+ if (new.nombre is distinct from old.nombre) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'nombre', to_jsonb(old.nombre), to_jsonb(new.nombre), null);
+ end if;
+
+ if (new.nivel is distinct from old.nivel) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'nivel', to_jsonb(old.nivel), to_jsonb(new.nivel), null);
+ end if;
+
+ if (new.tipo_ciclo is distinct from old.tipo_ciclo) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'tipo_ciclo', to_jsonb(old.tipo_ciclo), to_jsonb(new.tipo_ciclo), null);
+ end if;
+
+ if (new.numero_ciclos is distinct from old.numero_ciclos) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'numero_ciclos', to_jsonb(old.numero_ciclos), to_jsonb(new.numero_ciclos), null);
+ end if;
+
+ if (new.activo is distinct from old.activo) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'activo', to_jsonb(old.activo), to_jsonb(new.activo), null);
+ end if;
+
+ if (new.carrera_id is distinct from old.carrera_id) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'carrera_id', to_jsonb(old.carrera_id), to_jsonb(new.carrera_id), null);
+ end if;
+
+ if (new.estructura_id is distinct from old.estructura_id) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'estructura_id', to_jsonb(old.estructura_id), to_jsonb(new.estructura_id), null);
+ end if;
+
+ if (new.tipo_origen is distinct from old.tipo_origen) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'tipo_origen', to_jsonb(old.tipo_origen), to_jsonb(new.tipo_origen), null);
+ end if;
+
+
+ if (new.conversation_id is distinct from old.conversation_id) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'conversation_id', to_jsonb(old.conversation_id), to_jsonb(new.conversation_id), null);
+ end if;
+
+ -- 🔥 consumirlo para que NO se guarde en planes_estudio
+ if v_response_id is not null then
+ new.meta_origen := new.meta_origen - 'response_id';
+ end if;
+
+ return new;
+end;$function$
+;
+
+CREATE TRIGGER "agregar-conversation_id-asignaturas" AFTER INSERT ON public.asignaturas FOR EACH ROW EXECUTE FUNCTION supabase_functions.http_request('https://exdkssurzmjnnhgtiama.supabase.co/functions/v1/create-chat-conversation', 'POST', '{"Content-type":"application/json","Authorization":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImV4ZGtzc3Vyem1qbm5oZ3RpYW1hIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc0MTM3ODYzMiwiZXhwIjoyMDU2OTU0NjMyfQ.s-GHuwnYbIYoMZN9dFbAKgxNyAtQllRCRPLy-GIRaro"}', '{}', '5000');
+
+CREATE TRIGGER "agregar-conversation_id-planes_estudio" AFTER INSERT ON public.planes_estudio FOR EACH ROW EXECUTE FUNCTION supabase_functions.http_request('https://exdkssurzmjnnhgtiama.supabase.co/functions/v1/create-chat-conversation', 'POST', '{"Content-type":"application/json","Authorization":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImV4ZGtzc3Vyem1qbm5oZ3RpYW1hIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc0MTM3ODYzMiwiZXhwIjoyMDU2OTU0NjMyfQ.s-GHuwnYbIYoMZN9dFbAKgxNyAtQllRCRPLy-GIRaro"}', '{}', '5000');
+
+CREATE TRIGGER trg_planes_estudio_log_cambios AFTER INSERT OR DELETE OR UPDATE ON public.planes_estudio FOR EACH ROW EXECUTE FUNCTION public.fn_log_cambios_planes_estudio();
+
+
diff --git a/supabase/scheme_backup.sql b/supabase/scheme_backup.sql
index a056869..4006508 100644
--- a/supabase/scheme_backup.sql
+++ b/supabase/scheme_backup.sql
@@ -30,6 +30,13 @@ COMMENT ON SCHEMA "public" IS 'standard public schema';
+CREATE EXTENSION IF NOT EXISTS "http" WITH SCHEMA "extensions";
+
+
+
+
+
+
CREATE EXTENSION IF NOT EXISTS "pg_graphql" WITH SCHEMA "graphql";
@@ -159,7 +166,8 @@ CREATE TYPE "public"."tipo_cambio" AS ENUM (
'ACTUALIZACION_MAPA',
'TRANSICION_ESTADO',
'OTRO',
- 'CREACION'
+ 'CREACION',
+ 'ACTUALIZACION'
);
@@ -230,6 +238,172 @@ CREATE TYPE "public"."tipo_origen" AS ENUM (
ALTER TYPE "public"."tipo_origen" OWNER TO "postgres";
+CREATE OR REPLACE FUNCTION "public"."fn_log_cambios_planes_estudio"() RETURNS "trigger"
+ LANGUAGE "plpgsql"
+ AS $$declare
+ k text;
+ old_val jsonb;
+ new_val jsonb;
+
+ v_response_id text;
+begin
+ v_response_id := nullif(new.meta_origen->>'response_id','');
+
+ -- INSERT -> CREACION
+ if tg_op = 'INSERT' then
+ insert into public.cambios_plan (
+ plan_estudio_id,
+ cambiado_por,
+ tipo,
+ campo,
+ valor_anterior,
+ valor_nuevo,
+ response_id
+ )
+ values (
+ new.id,
+ new.creado_por,
+ 'CREACION'::public.tipo_cambio,
+ null,
+ null,
+ to_jsonb(new),
+ null
+ );
+
+ return new;
+ end if;
+
+ -- DELETE (opcional): si no lo quieres, bórralo
+ if tg_op = 'DELETE' then
+ insert into public.cambios_plan (
+ plan_estudio_id,
+ cambiado_por,
+ tipo,
+ campo,
+ valor_anterior,
+ valor_nuevo,
+ response_id
+ )
+ values (
+ old.id,
+ old.actualizado_por,
+ 'OTRO'::public.tipo_cambio,
+ 'DELETE',
+ to_jsonb(old),
+ null,
+ null
+ );
+
+ return old;
+ end if;
+
+ -- UPDATE ----------------------------------------------------------
+ -- 1) Transición de estado
+ if (new.estado_actual_id is distinct from old.estado_actual_id) then
+ insert into public.cambios_plan (
+ plan_estudio_id, cambiado_por, tipo, campo, valor_anterior, valor_nuevo, response_id
+ )
+ values (
+ new.id,
+ new.actualizado_por,
+ 'TRANSICION_ESTADO'::public.tipo_cambio,
+ 'estado_actual_id',
+ to_jsonb(old.estado_actual_id),
+ to_jsonb(new.estado_actual_id),
+ null
+ );
+ end if;
+
+ -- 2) Cambios en JSONB "datos" (diff top-level por llave)
+ if (new.datos is distinct from old.datos) then
+ for k in
+ select distinct key
+ from (
+ select jsonb_object_keys(coalesce(old.datos, '{}'::jsonb)) as key
+ union all
+ select jsonb_object_keys(coalesce(new.datos, '{}'::jsonb)) as key
+ ) t
+ loop
+ old_val := coalesce(old.datos, '{}'::jsonb) -> k;
+ new_val := coalesce(new.datos, '{}'::jsonb) -> k;
+
+ if (old_val is distinct from new_val) then
+ insert into public.cambios_plan (
+ plan_estudio_id, cambiado_por, tipo, campo, valor_anterior, valor_nuevo, response_id
+ )
+ values (
+ new.id,
+ new.actualizado_por,
+ 'ACTUALIZACION_CAMPO'::public.tipo_cambio,
+ k,
+ old_val,
+ new_val,
+ v_response_id
+ );
+ end if;
+ end loop;
+
+ end if;
+
+ -- 3) Cambios en columnas "normales" (uno por columna)
+ if (new.nombre is distinct from old.nombre) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'nombre', to_jsonb(old.nombre), to_jsonb(new.nombre), null);
+ end if;
+
+ if (new.nivel is distinct from old.nivel) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'nivel', to_jsonb(old.nivel), to_jsonb(new.nivel), null);
+ end if;
+
+ if (new.tipo_ciclo is distinct from old.tipo_ciclo) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'tipo_ciclo', to_jsonb(old.tipo_ciclo), to_jsonb(new.tipo_ciclo), null);
+ end if;
+
+ if (new.numero_ciclos is distinct from old.numero_ciclos) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'numero_ciclos', to_jsonb(old.numero_ciclos), to_jsonb(new.numero_ciclos), null);
+ end if;
+
+ if (new.activo is distinct from old.activo) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'activo', to_jsonb(old.activo), to_jsonb(new.activo), null);
+ end if;
+
+ if (new.carrera_id is distinct from old.carrera_id) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'carrera_id', to_jsonb(old.carrera_id), to_jsonb(new.carrera_id), null);
+ end if;
+
+ if (new.estructura_id is distinct from old.estructura_id) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'estructura_id', to_jsonb(old.estructura_id), to_jsonb(new.estructura_id), null);
+ end if;
+
+ if (new.tipo_origen is distinct from old.tipo_origen) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'tipo_origen', to_jsonb(old.tipo_origen), to_jsonb(new.tipo_origen), null);
+ end if;
+
+
+ if (new.conversation_id is distinct from old.conversation_id) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'conversation_id', to_jsonb(old.conversation_id), to_jsonb(new.conversation_id), null);
+ end if;
+
+ -- 🔥 consumirlo para que NO se guarde en planes_estudio
+ if v_response_id is not null then
+ new.meta_origen := new.meta_origen - 'response_id';
+ end if;
+
+ return new;
+end;$$;
+
+
+ALTER FUNCTION "public"."fn_log_cambios_planes_estudio"() OWNER TO "postgres";
+
+
CREATE OR REPLACE FUNCTION "public"."set_actualizado_en"() RETURNS "trigger"
LANGUAGE "plpgsql"
AS $$
@@ -246,7 +420,7 @@ ALTER FUNCTION "public"."set_actualizado_en"() OWNER TO "postgres";
CREATE OR REPLACE FUNCTION "public"."unaccent_immutable"("text") RETURNS "text"
LANGUAGE "sql" IMMUTABLE STRICT PARALLEL SAFE
AS $_$
- SELECT unaccent('unaccent', $1);
+ SELECT public.unaccent('public.unaccent', $1);
$_$;
@@ -313,7 +487,6 @@ CREATE TABLE IF NOT EXISTS "public"."asignaturas" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"plan_estudio_id" "uuid" NOT NULL,
"estructura_id" "uuid",
- "facultad_propietaria_id" "uuid",
"codigo" "text",
"nombre" "text" NOT NULL,
"tipo" "public"."tipo_asignatura" DEFAULT 'OBLIGATORIA'::"public"."tipo_asignatura" NOT NULL,
@@ -330,6 +503,7 @@ CREATE TABLE IF NOT EXISTS "public"."asignaturas" (
"actualizado_por" "uuid",
"creado_en" timestamp with time zone DEFAULT "now"() NOT NULL,
"actualizado_en" timestamp with time zone DEFAULT "now"() NOT NULL,
+ "asignatura_hash" "text" GENERATED ALWAYS AS ("encode"(SUBSTRING("extensions"."digest"(("id")::"text", 'sha512'::"text") FROM 1 FOR 12), 'hex'::"text")) STORED,
CONSTRAINT "asignaturas_ciclo_chk" CHECK ((("numero_ciclo" IS NULL) OR ("numero_ciclo" > 0))),
CONSTRAINT "asignaturas_creditos_check" CHECK (("creditos" >= (0)::numeric)),
CONSTRAINT "asignaturas_horas_semana_check" CHECK ((("horas_semana" IS NULL) OR ("horas_semana" >= 0))),
@@ -382,13 +556,17 @@ CREATE TABLE IF NOT EXISTS "public"."cambios_plan" (
"campo" "text",
"valor_anterior" "jsonb",
"valor_nuevo" "jsonb",
- "interaccion_ia_id" "uuid"
+ "response_id" "text"
);
ALTER TABLE "public"."cambios_plan" OWNER TO "postgres";
+COMMENT ON COLUMN "public"."cambios_plan"."response_id" IS 'El ID de respuesta de OpenAI';
+
+
+
CREATE TABLE IF NOT EXISTS "public"."carreras" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"facultad_id" "uuid" NOT NULL,
@@ -525,6 +703,8 @@ CREATE TABLE IF NOT EXISTS "public"."planes_estudio" (
"creado_en" timestamp with time zone DEFAULT "now"() NOT NULL,
"actualizado_en" timestamp with time zone DEFAULT "now"() NOT NULL,
"nombre_search" "text" GENERATED ALWAYS AS ("lower"("public"."unaccent_immutable"("nombre"))) STORED,
+ "plan_hash" "text" GENERATED ALWAYS AS ("encode"(SUBSTRING("extensions"."digest"(("id")::"text", 'sha512'::"text") FROM 1 FOR 12), 'hex'::"text")) STORED,
+ "conversation_id" "text",
CONSTRAINT "planes_estudio_numero_ciclos_check" CHECK (("numero_ciclos" > 0))
);
@@ -532,6 +712,10 @@ CREATE TABLE IF NOT EXISTS "public"."planes_estudio" (
ALTER TABLE "public"."planes_estudio" OWNER TO "postgres";
+COMMENT ON COLUMN "public"."planes_estudio"."conversation_id" IS 'ID de conversación de OpenAI';
+
+
+
CREATE OR REPLACE VIEW "public"."plantilla_plan" AS
SELECT "plan"."id" AS "plan_estudio_id",
"struct"."id" AS "estructura_id",
@@ -714,6 +898,11 @@ ALTER TABLE ONLY "public"."notificaciones"
+ALTER TABLE ONLY "public"."planes_estudio"
+ ADD CONSTRAINT "planes_estudio_conversation_id_key" UNIQUE ("conversation_id");
+
+
+
ALTER TABLE ONLY "public"."planes_estudio"
ADD CONSTRAINT "planes_estudio_pkey" PRIMARY KEY ("id");
@@ -826,6 +1015,10 @@ CREATE OR REPLACE TRIGGER "trg_planes_estudio_actualizado_en" BEFORE UPDATE ON "
+CREATE OR REPLACE TRIGGER "trg_planes_estudio_log_cambios" AFTER INSERT OR DELETE OR UPDATE ON "public"."planes_estudio" FOR EACH ROW EXECUTE FUNCTION "public"."fn_log_cambios_planes_estudio"();
+
+
+
CREATE OR REPLACE TRIGGER "trg_usuarios_app_actualizado_en" BEFORE UPDATE ON "public"."usuarios_app" FOR EACH ROW EXECUTE FUNCTION "public"."set_actualizado_en"();
@@ -854,11 +1047,6 @@ ALTER TABLE ONLY "public"."asignaturas"
-ALTER TABLE ONLY "public"."asignaturas"
- ADD CONSTRAINT "asignaturas_facultad_propietaria_id_fkey" FOREIGN KEY ("facultad_propietaria_id") REFERENCES "public"."facultades"("id") ON DELETE SET NULL;
-
-
-
ALTER TABLE ONLY "public"."asignaturas"
ADD CONSTRAINT "asignaturas_linea_plan_fk_compuesta" FOREIGN KEY ("linea_plan_id", "plan_estudio_id") REFERENCES "public"."lineas_plan"("id", "plan_estudio_id") ON DELETE SET NULL;
@@ -894,11 +1082,6 @@ ALTER TABLE ONLY "public"."cambios_plan"
-ALTER TABLE ONLY "public"."cambios_plan"
- ADD CONSTRAINT "cambios_plan_plan_estudio_id_fkey" FOREIGN KEY ("plan_estudio_id") REFERENCES "public"."planes_estudio"("id") ON DELETE CASCADE;
-
-
-
ALTER TABLE ONLY "public"."carreras"
ADD CONSTRAINT "carreras_facultad_id_fkey" FOREIGN KEY ("facultad_id") REFERENCES "public"."facultades"("id") ON DELETE RESTRICT;
@@ -1220,6 +1403,69 @@ GRANT USAGE ON SCHEMA "public" TO "service_role";
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+GRANT ALL ON FUNCTION "public"."fn_log_cambios_planes_estudio"() TO "anon";
+GRANT ALL ON FUNCTION "public"."fn_log_cambios_planes_estudio"() TO "authenticated";
+GRANT ALL ON FUNCTION "public"."fn_log_cambios_planes_estudio"() TO "service_role";
+
+
+
GRANT ALL ON FUNCTION "public"."set_actualizado_en"() TO "anon";
GRANT ALL ON FUNCTION "public"."set_actualizado_en"() TO "authenticated";
GRANT ALL ON FUNCTION "public"."set_actualizado_en"() TO "service_role";
diff --git a/supabase/snippets/Untitled query 238.sql b/supabase/snippets/Untitled query 238.sql
new file mode 100644
index 0000000..133a53f
--- /dev/null
+++ b/supabase/snippets/Untitled query 238.sql
@@ -0,0 +1,205 @@
+create extension if not exists "http" with schema "extensions";
+
+alter table "public"."asignaturas" drop constraint if exists "asignaturas_facultad_propietaria_id_fkey";
+
+alter table "public"."cambios_plan" drop constraint "cambios_plan_plan_estudio_id_fkey";
+
+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', 'ACTUALIZACION');
+
+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";
+
+alter table "public"."asignaturas" drop column if exists "facultad_propietaria_id";
+
+alter table "public"."asignaturas" add column "asignatura_hash" text generated always as (encode(SUBSTRING(extensions.digest((id)::text, 'sha512'::text) FROM 1 FOR 12), 'hex'::text)) stored;
+
+alter table "public"."cambios_plan" drop column "interaccion_ia_id";
+
+alter table "public"."cambios_plan" add column "response_id" text;
+
+alter table "public"."planes_estudio" add column "conversation_id" text;
+
+alter table "public"."planes_estudio" add column "plan_hash" text generated always as (encode(SUBSTRING(extensions.digest((id)::text, 'sha512'::text) FROM 1 FOR 12), 'hex'::text)) stored;
+
+CREATE UNIQUE INDEX planes_estudio_conversation_id_key ON public.planes_estudio USING btree (conversation_id);
+
+alter table "public"."planes_estudio" add constraint "planes_estudio_conversation_id_key" UNIQUE using index "planes_estudio_conversation_id_key";
+
+set check_function_bodies = off;
+
+CREATE OR REPLACE FUNCTION public.fn_log_cambios_planes_estudio()
+ RETURNS trigger
+ LANGUAGE plpgsql
+AS $function$declare
+ k text;
+ old_val jsonb;
+ new_val jsonb;
+
+ v_response_id text;
+begin
+ v_response_id := nullif(new.meta_origen->>'response_id','');
+
+ -- INSERT -> CREACION
+ if tg_op = 'INSERT' then
+ insert into public.cambios_plan (
+ plan_estudio_id,
+ cambiado_por,
+ tipo,
+ campo,
+ valor_anterior,
+ valor_nuevo,
+ response_id
+ )
+ values (
+ new.id,
+ new.creado_por,
+ 'CREACION'::public.tipo_cambio,
+ null,
+ null,
+ to_jsonb(new),
+ null
+ );
+
+ return new;
+ end if;
+
+ -- DELETE (opcional): si no lo quieres, bórralo
+ if tg_op = 'DELETE' then
+ insert into public.cambios_plan (
+ plan_estudio_id,
+ cambiado_por,
+ tipo,
+ campo,
+ valor_anterior,
+ valor_nuevo,
+ response_id
+ )
+ values (
+ old.id,
+ old.actualizado_por,
+ 'OTRO'::public.tipo_cambio,
+ 'DELETE',
+ to_jsonb(old),
+ null,
+ null
+ );
+
+ return old;
+ end if;
+
+ -- UPDATE ----------------------------------------------------------
+ -- 1) Transición de estado
+ if (new.estado_actual_id is distinct from old.estado_actual_id) then
+ insert into public.cambios_plan (
+ plan_estudio_id, cambiado_por, tipo, campo, valor_anterior, valor_nuevo, response_id
+ )
+ values (
+ new.id,
+ new.actualizado_por,
+ 'TRANSICION_ESTADO'::public.tipo_cambio,
+ 'estado_actual_id',
+ to_jsonb(old.estado_actual_id),
+ to_jsonb(new.estado_actual_id),
+ null
+ );
+ end if;
+
+ -- 2) Cambios en JSONB "datos" (diff top-level por llave)
+ if (new.datos is distinct from old.datos) then
+ for k in
+ select distinct key
+ from (
+ select jsonb_object_keys(coalesce(old.datos, '{}'::jsonb)) as key
+ union all
+ select jsonb_object_keys(coalesce(new.datos, '{}'::jsonb)) as key
+ ) t
+ loop
+ old_val := coalesce(old.datos, '{}'::jsonb) -> k;
+ new_val := coalesce(new.datos, '{}'::jsonb) -> k;
+
+ if (old_val is distinct from new_val) then
+ insert into public.cambios_plan (
+ plan_estudio_id, cambiado_por, tipo, campo, valor_anterior, valor_nuevo, response_id
+ )
+ values (
+ new.id,
+ new.actualizado_por,
+ 'ACTUALIZACION_CAMPO'::public.tipo_cambio,
+ k,
+ old_val,
+ new_val,
+ v_response_id
+ );
+ end if;
+ end loop;
+
+ end if;
+
+ -- 3) Cambios en columnas "normales" (uno por columna)
+ if (new.nombre is distinct from old.nombre) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'nombre', to_jsonb(old.nombre), to_jsonb(new.nombre), null);
+ end if;
+
+ if (new.nivel is distinct from old.nivel) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'nivel', to_jsonb(old.nivel), to_jsonb(new.nivel), null);
+ end if;
+
+ if (new.tipo_ciclo is distinct from old.tipo_ciclo) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'tipo_ciclo', to_jsonb(old.tipo_ciclo), to_jsonb(new.tipo_ciclo), null);
+ end if;
+
+ if (new.numero_ciclos is distinct from old.numero_ciclos) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'numero_ciclos', to_jsonb(old.numero_ciclos), to_jsonb(new.numero_ciclos), null);
+ end if;
+
+ if (new.activo is distinct from old.activo) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'activo', to_jsonb(old.activo), to_jsonb(new.activo), null);
+ end if;
+
+ if (new.carrera_id is distinct from old.carrera_id) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'carrera_id', to_jsonb(old.carrera_id), to_jsonb(new.carrera_id), null);
+ end if;
+
+ if (new.estructura_id is distinct from old.estructura_id) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'estructura_id', to_jsonb(old.estructura_id), to_jsonb(new.estructura_id), null);
+ end if;
+
+ if (new.tipo_origen is distinct from old.tipo_origen) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'tipo_origen', to_jsonb(old.tipo_origen), to_jsonb(new.tipo_origen), null);
+ end if;
+
+
+ if (new.conversation_id is distinct from old.conversation_id) then
+ insert into public.cambios_plan values (gen_random_uuid(), new.id, new.actualizado_por, now(),
+ 'ACTUALIZACION'::public.tipo_cambio, 'conversation_id', to_jsonb(old.conversation_id), to_jsonb(new.conversation_id), null);
+ end if;
+
+ -- 🔥 consumirlo para que NO se guarde en planes_estudio
+ if v_response_id is not null then
+ new.meta_origen := new.meta_origen - 'response_id';
+ end if;
+
+ return new;
+end;$function$
+;
+
+CREATE TRIGGER "agregar-conversation_id-asignaturas" AFTER INSERT ON public.asignaturas FOR EACH ROW EXECUTE FUNCTION supabase_functions.http_request('https://exdkssurzmjnnhgtiama.supabase.co/functions/v1/create-chat-conversation', 'POST', '{"Content-type":"application/json","Authorization":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImV4ZGtzc3Vyem1qbm5oZ3RpYW1hIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc0MTM3ODYzMiwiZXhwIjoyMDU2OTU0NjMyfQ.s-GHuwnYbIYoMZN9dFbAKgxNyAtQllRCRPLy-GIRaro"}', '{}', '5000');
+
+CREATE TRIGGER "agregar-conversation_id-planes_estudio" AFTER INSERT ON public.planes_estudio FOR EACH ROW EXECUTE FUNCTION supabase_functions.http_request('https://exdkssurzmjnnhgtiama.supabase.co/functions/v1/create-chat-conversation', 'POST', '{"Content-type":"application/json","Authorization":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImV4ZGtzc3Vyem1qbm5oZ3RpYW1hIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc0MTM3ODYzMiwiZXhwIjoyMDU2OTU0NjMyfQ.s-GHuwnYbIYoMZN9dFbAKgxNyAtQllRCRPLy-GIRaro"}', '{}', '5000');
+
+CREATE TRIGGER trg_planes_estudio_log_cambios AFTER INSERT OR DELETE OR UPDATE ON public.planes_estudio FOR EACH ROW EXECUTE FUNCTION public.fn_log_cambios_planes_estudio();
+
+
diff --git a/supabase/snippets/inserts a facultades y a carreras.sql b/supabase/snippets/inserts a facultades y a carreras.sql
new file mode 100644
index 0000000..71c6556
--- /dev/null
+++ b/supabase/snippets/inserts a facultades y a carreras.sql
@@ -0,0 +1,128 @@
+INSERT INTO public.facultades (nombre, nombre_corto, color, icono) VALUES
+--('Otros', 'OTRO', '#64748B', 'MoreHorizontal'),
+('Coordinación de Desarrollo Humano Profesional', 'CDHP', '#F472B6', 'HeartHandshake'),
+('Centro de Idiomas', 'CI', '#2DD4BF', 'Languages'),
+--('Campus Virtual La Salle', 'CVLS', '#8B5CF6', 'Monitor'),
+('Escuela de Altos Estudios en Salud', 'EAES', '#F43F5E', 'Stethoscope'),
+--('Escuela Preparatoria Unidad Santa Teresa', 'EPST', '#EAB308', 'Backpack'),
+--('Escuela Preparatoria Unidad Condesa', 'EPUC', '#F59E0B', 'BookOpen'),
+('Facultad Mexicana de Arquitectura, Diseño y Comunicación', 'FAMADYC', '#EC4899', 'DraftingCompass'),
+--('Facultad de Negocios', 'NEG', '#3B82F6', 'Briefcase'),
+('Facultad de Derecho', 'DER', '#64748B', 'Scale'),
+('Facultad de Humanidades y Ciencias Sociales', 'HUM', '#6366F1', 'Users'),
+--('Facultad de Ingeniería', 'ING', '#F97316', 'Cpu'),
+('Facultad Mexicana de Medicina', 'MED', '#10B981', 'HeartPulse'),
+('Facultad de Ciencias Químicas', 'FCQ', '#84CC16', 'FlaskConical'),
+('Escuela de Ciencias Religiosas', 'ECR', '#0EA5E9', 'BookHeart'),
+('Escuela de Ciencias de la Educación', 'EDU', '#FB7185', 'GraduationCap');
+--('Dirección Campus Virtual', 'DCV', '#A855F7', 'Globe');
+
+INSERT INTO public.carreras (facultad_id, nombre, nombre_corto) VALUES
+-- Coordinación de Desarrollo Humano Profesional
+((SELECT id FROM public.facultades WHERE nombre = 'Coordinación de Desarrollo Humano Profesional' LIMIT 1), 'Área Curricular Común 2013', 'ACC'),
+((SELECT id FROM public.facultades WHERE nombre = 'Coordinación de Desarrollo Humano Profesional' LIMIT 1), 'Área Curricular Común 2021', 'ACC'),
+
+-- Centro de Idiomas
+((SELECT id FROM public.facultades WHERE nombre = 'Centro de Idiomas' LIMIT 1), 'Área Curricular Común (Centro de Idiomas) 2013', 'ACCI'),
+((SELECT id FROM public.facultades WHERE nombre = 'Centro de Idiomas' LIMIT 1), 'Área Curricular Común (Centro de Idiomas) 2021', 'ACCI'),
+
+-- Escuela de Altos Estudios en Salud
+((SELECT id FROM public.facultades WHERE nombre = 'Escuela de Altos Estudios en Salud' LIMIT 1), 'Licenciatura en Enfermería', 'LENF'),
+((SELECT id FROM public.facultades WHERE nombre = 'Escuela de Altos Estudios en Salud' LIMIT 1), 'Licenciatura en Fisioterapia y Promoción para la Salud', 'LFPS'),
+((SELECT id FROM public.facultades WHERE nombre = 'Escuela de Altos Estudios en Salud' LIMIT 1), 'Licenciatura en Ciencias en el Deporte', 'LCD'),
+((SELECT id FROM public.facultades WHERE nombre = 'Escuela de Altos Estudios en Salud' LIMIT 1), 'Licenciatura en Medicina Dental', 'LMD'),
+((SELECT id FROM public.facultades WHERE nombre = 'Escuela de Altos Estudios en Salud' LIMIT 1), 'Maestría en Nutrición Clínica', 'MNC'),
+((SELECT id FROM public.facultades WHERE nombre = 'Escuela de Altos Estudios en Salud' LIMIT 1), 'Licenciatura en Fisioterapia', 'LFIS'),
+
+-- Facultad Mexicana de Arquitectura, Diseño y Comunicación
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad Mexicana de Arquitectura, Diseño y Comunicación' LIMIT 1), 'Licenciatura en Arquitectura', 'ARQ'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad Mexicana de Arquitectura, Diseño y Comunicación' LIMIT 1), 'Licenciatura en Ciencias de la Comunicación', 'LCC'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad Mexicana de Arquitectura, Diseño y Comunicación' LIMIT 1), 'Licenciatura en Diseño Gráfico y Digital', 'LDGD'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad Mexicana de Arquitectura, Diseño y Comunicación' LIMIT 1), 'Licenciatura en Diseño de Ambientes Interiores y Exteriores', 'LDAI'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad Mexicana de Arquitectura, Diseño y Comunicación' LIMIT 1), 'Especialidad en Gestión y Administración de Proyectos', 'EGAP'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad Mexicana de Arquitectura, Diseño y Comunicación' LIMIT 1), 'Maestría en Estrategia e Innovación en Marcas', 'MEIM'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad Mexicana de Arquitectura, Diseño y Comunicación' LIMIT 1), 'Maestría en Dirección Estratégica de Comunicación', 'MDEC'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad Mexicana de Arquitectura, Diseño y Comunicación' LIMIT 1), 'Licenciatura en Comunicación', 'LCOM'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad Mexicana de Arquitectura, Diseño y Comunicación' LIMIT 1), 'Licenciatura en Diseño de Productos', 'LDP'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad Mexicana de Arquitectura, Diseño y Comunicación' LIMIT 1), 'Especialidad en Dirección de Proyectos', 'EDP'),
+
+-- Facultad de Derecho
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Derecho' LIMIT 1), 'Licenciatura en Derecho', 'LDER'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Derecho' LIMIT 1), 'Licenciatura en Relaciones Internacionales', 'LRI'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Derecho' LIMIT 1), 'Maestría en Derecho Financiero', 'MDF'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Derecho' LIMIT 1), 'Especialidad en Derecho de Empresa', 'EDE'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Derecho' LIMIT 1), 'Maestría en Gobernanza y Estrategia Internacional', 'MGEI'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Derecho' LIMIT 1), 'Doctorado en Ciencias Jurídicas', 'DCJ'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Derecho' LIMIT 1), 'Maestría en Derecho Civil', 'MDC'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Derecho' LIMIT 1), 'Maestría en Derecho de Empresa', 'MDE'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Derecho' LIMIT 1), 'Maestría en Justicia Penal', 'MJP'),
+
+-- Facultad de Humanidades y Ciencias Sociales
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Humanidades y Ciencias Sociales' LIMIT 1), 'Licenciatura en Educación Preescolar', 'LEP'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Humanidades y Ciencias Sociales' LIMIT 1), 'Licenciatura en Ciencias Religiosas', 'LCR'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Humanidades y Ciencias Sociales' LIMIT 1), 'Licenciatura en Filosofía', 'LFIL'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Humanidades y Ciencias Sociales' LIMIT 1), 'Maestría en Filosofía Social', 'MFS'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Humanidades y Ciencias Sociales' LIMIT 1), 'Área de Gestión Educativa', 'AGE'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Humanidades y Ciencias Sociales' LIMIT 1), 'Área de Intervención Docente', 'AID'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Humanidades y Ciencias Sociales' LIMIT 1), 'Especialidad en Gestión de los Aprendizajes', 'EGA'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Humanidades y Ciencias Sociales' LIMIT 1), 'Doctorado en Educación', 'DOCE'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Humanidades y Ciencias Sociales' LIMIT 1), 'Licenciatura en Psicología', 'LPS'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Humanidades y Ciencias Sociales' LIMIT 1), 'Licenciatura en Educación Primaria', 'LEPR'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Humanidades y Ciencias Sociales' LIMIT 1), 'Licenciatura en Ciencias de la Educación', 'LCE'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Humanidades y Ciencias Sociales' LIMIT 1), 'Licenciatura en Pedagogía', 'LPED'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Humanidades y Ciencias Sociales' LIMIT 1), 'Licenciatura en Teología', 'LTEO'),
+
+-- Facultad Mexicana de Medicina
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad Mexicana de Medicina' LIMIT 1), 'Licenciatura en Médico Cirujano', 'LMC'),
+
+-- Facultad de Ciencias Químicas
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ciencias Químicas' LIMIT 1), 'Licenciatura en Ingeniería Química', 'LIQ'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ciencias Químicas' LIMIT 1), 'Licenciatura en Químico Farmacéutico Biólogo', 'QFB'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ciencias Químicas' LIMIT 1), 'Licenciatura en Química de Alimentos', 'LQA'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ciencias Químicas' LIMIT 1), 'Licenciatura en Ingeniería Ambiental', 'LIA'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ciencias Químicas' LIMIT 1), 'Maestría en Ciencia de los Alimentos y Nutrición Humana', 'MCAN'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ciencias Químicas' LIMIT 1), 'Maestría en Farmacología Clínica', 'MFC'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ciencias Químicas' LIMIT 1), 'Maestría en Calidad y Estadística Aplicada', 'MCEA'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ciencias Químicas' LIMIT 1), 'Maestría en Ingeniería de Proyectos', 'MIP'),
+
+-- Facultad de Negocios
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Licenciatura en Contaduría y Finanzas', 'LCF'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Licenciatura en Mercadotecnia', 'LMER'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Licenciatura en Comercio y Negocios Internacionales', 'LCNI'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Licenciatura en Administración', 'LADM'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Licenciatura en Tecnologías de Información', 'LTI'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Maestría en Dirección de Organizaciones de la Salud', 'MDOS'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Licenciatura en Actuaría', 'LACT'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Especialidad en Mercadotecnia y Publicidad', 'EMP'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Especialidad en Finanzas Corporativas y Bursátiles', 'EFCB'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Especialidad en Logística y Cadena de Suministro', 'ELCS'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Especialidad en Administración de Organizaciones de la Salud', 'EAOS'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Maestría en Ciencias Actuariales', 'MCA'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Doctorado en Administración', 'DADM'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Maestría en Administración de Organizaciones de la Salud', 'MAOS'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Maestría en Administración de Negocios Internacionales', 'MANI'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Maestría en Tecnologías de Información en la Dirección de Negocios', 'MTID'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Maestría en Administración', 'MADM'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Maestría en Ingeniería Económica y Financiera', 'MIEF'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Maestría en Administración de Negocios Internacionales (MIEX)', 'MIEX'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Maestría en Gestión Estratégica del Capital Humano', 'MGEC'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Licenciatura en Administración e Innovación de Negocios', 'LAIN'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Licenciatura en Ingeniería Económica y Financiera', 'LIEF'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Negocios' LIMIT 1), 'Licenciatura en Transformación Digital para los Negocios', 'LTDN'),
+
+-- Facultad de Ingeniería
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ingeniería' LIMIT 1), 'Licenciatura en Ingeniería Electrónica', 'LIE'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ingeniería' LIMIT 1), 'Licenciatura en Ingeniería Civil', 'LIC'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ingeniería' LIMIT 1), 'Licenciatura en Ingeniería Mecánica y en Sistemas Energéticos', 'LIME'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ingeniería' LIMIT 1), 'Licenciatura en Ingeniería Electrónica para Sistemas Inteligentes', 'LIES'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ingeniería' LIMIT 1), 'Licenciatura en Ingeniería Mecatrónica', 'LIM'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ingeniería' LIMIT 1), 'Licenciatura en Ingeniería Industrial', 'LII'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ingeniería' LIMIT 1), 'Licenciatura en Ingeniería Cibernética y Sistemas Computacionales', 'LICS'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ingeniería' LIMIT 1), 'Licenciatura en Ingeniería Biomédica', 'LIB'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ingeniería' LIMIT 1), 'Maestría en Gerencia de Proyectos Inmobiliarios', 'MGPI'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ingeniería' LIMIT 1), 'Maestría en Dirección Industrial', 'MDI'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ingeniería' LIMIT 1), 'Especialidad en Dirección Industrial', 'EDI'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ingeniería' LIMIT 1), 'Maestría en Gestión de Proyectos y de Empresas Constructoras', 'MGPC'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ingeniería' LIMIT 1), 'Especialidad en Energías Renovables', 'EER'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ingeniería' LIMIT 1), 'Maestría en Ciberseguridad', 'MCIB'),
+((SELECT id FROM public.facultades WHERE nombre = 'Facultad de Ingeniería' LIMIT 1), 'Especialidad en Inteligencia de Datos', 'EID');
\ No newline at end of file