// ===================================================================== // Reconstruye la base del portal desde cero: esquema + datos. // node db/seed.mjs (o npm run db:reset) // ===================================================================== import { readFile } from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; import path from 'node:path'; import mysql from 'mysql2/promise'; import { config } from '../server/config.mjs'; import * as D from './data.mjs'; const raiz = path.dirname(fileURLToPath(import.meta.url)); function log(paso, detalle = '') { console.log(` ${paso.padEnd(34, '.')} ${detalle}`); } async function main() { const cn = await mysql.createConnection({ ...config.db, multipleStatements: true, charset: 'utf8mb4' }); console.log(`\n▸ Portal INSTA · siembra de "${config.db.database}" en ${config.db.host}:${config.db.port}\n`); const schema = await readFile(path.join(raiz, 'schema.sql'), 'utf8'); await cn.query(schema); log('esquema reconstruido', `${schema.split('CREATE TABLE').length - 1} tablas`); // ---- roles, permisos y usuarios ------------------------------------- const rolId = {}; for (const r of D.roles) { const [res] = await cn.execute( 'INSERT INTO roles (clave, nombre, nivel, color, descripcion) VALUES (?,?,?,?,?)', [r.clave, r.nombre, r.nivel, r.color, r.descripcion], ); rolId[r.clave] = res.insertId; } const permisoId = {}; for (const [clave, seccion, descripcion] of D.permisos) { const [res] = await cn.execute( 'INSERT INTO permisos (clave, seccion, descripcion) VALUES (?,?,?)', [clave, seccion, descripcion], ); permisoId[clave] = res.insertId; } let asignaciones = 0; for (const [rolClave, lista] of Object.entries(D.rolesPermisos)) { const claves = lista === '*' ? Object.keys(permisoId) : lista; for (const c of claves) { if (!permisoId[c]) throw new Error(`Permiso desconocido en rolesPermisos.${rolClave}: ${c}`); await cn.execute('INSERT INTO roles_permisos (rol_id, permiso_id) VALUES (?,?)', [rolId[rolClave], permisoId[c]]); asignaciones++; } } log('roles y permisos', `${D.roles.length} roles · ${D.permisos.length} permisos · ${asignaciones} asignaciones`); const usuarioId = {}; for (const u of D.usuarios) { const [res] = await cn.execute( 'INSERT INTO usuarios (nombre, email, puesto, rol_id, iniciales, extension, es_demo) VALUES (?,?,?,?,?,?,?)', [u.nombre, u.email, u.puesto, rolId[u.rol], u.iniciales, u.extension, u.es_demo], ); usuarioId[u.email] = res.insertId; } log('usuarios', `${Object.keys(usuarioId).length} (3 en el acceso rápido)`); // ---- oferta educativa ----------------------------------------------- const nivelId = {}; for (const n of D.niveles) { const [res] = await cn.execute('INSERT INTO niveles (clave, nombre, orden) VALUES (?,?,?)', [n.clave, n.nombre, n.orden]); nivelId[n.clave] = res.insertId; } const modalidadId = {}; for (const m of D.modalidades) { const [res] = await cn.execute( 'INSERT INTO modalidades (clave, nombre, color, detalle) VALUES (?,?,?,?)', [m.clave, m.nombre, m.color, m.detalle], ); modalidadId[m.clave] = res.insertId; } const programaId = {}; let horarios = 0; for (const p of D.programas) { const [res] = await cn.execute( `INSERT INTO programas (nivel_id, clave, nombre, nombre_corto, duracion, fecha_inicio, inscripcion_lista, inscripcion_beca, mensualidad_lista, mensualidad_beca, ciclo_semanas, rvoe_acuerdo, rvoe_fecha, rvoe_autoridad, rvoe_estado, rvoe_nota, beneficios, color, orden) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, [nivelId[p.nivel], p.clave, p.nombre, p.nombre_corto, p.duracion, p.fecha_inicio, p.inscripcion_lista, p.inscripcion_beca, p.mensualidad_lista, p.mensualidad_beca, p.ciclo_semanas, p.rvoe_acuerdo, p.rvoe_fecha, p.rvoe_autoridad, p.rvoe_estado, p.rvoe_nota, p.beneficios, p.color, p.orden], ); programaId[p.clave] = res.insertId; for (const h of p.horarios) { await cn.execute( 'INSERT INTO programas_modalidades (programa_id, modalidad_id, dias, hora_inicio, hora_fin, nota) VALUES (?,?,?,?,?,?)', [res.insertId, modalidadId[h.modalidad], h.dias, h.hora_inicio, h.hora_fin, h.nota], ); horarios++; } } log('programas', `${Object.keys(programaId).length} programas · ${horarios} horarios`); let reqs = 0; for (const [nivelClave, lista] of Object.entries(D.requisitos)) { let orden = 1; for (const [texto, nota] of lista) { await cn.execute('INSERT INTO requisitos (nivel_id, texto, nota, orden) VALUES (?,?,?,?)', [nivelId[nivelClave], texto, nota, orden++]); reqs++; } } let ordenTit = 1; for (const [nombre, descripcion] of D.titulacionOpciones) { await cn.execute('INSERT INTO titulacion_opciones (nombre, descripcion, orden) VALUES (?,?,?)', [nombre, descripcion, ordenTit++]); } log('requisitos y titulación', `${reqs} requisitos · ${D.titulacionOpciones.length} formas de titulación`); // ---- becas y promociones -------------------------------------------- for (const p of D.promociones) { await cn.execute( 'INSERT INTO promociones (clave, nombre, resumen, aplica_a, vigencia, acumulable, condiciones, orden) VALUES (?,?,?,?,?,?,?,?)', [p.clave, p.nombre, p.resumen, p.aplica_a, p.vigencia, p.acumulable, p.condiciones, p.orden], ); } log('becas y promociones', `${D.promociones.length}`); // ---- links de clases ------------------------------------------------- let ordenClase = 1; for (const c of D.clases) { if (!programaId[c.programa]) throw new Error(`Programa desconocido en clases: ${c.programa}`); await cn.execute( `INSERT INTO clases (programa_id, materia, docente, grupo, dias, hora_inicio, hora_fin, enlace, notas, orden) VALUES (?,?,?,?,?,?,?,?,?,?)`, [programaId[c.programa], c.materia, c.docente, c.grupo, c.dias, c.hora_inicio, c.hora_fin, c.enlace, c.notas, ordenClase++], ); } log('links de clases', `${D.clases.length} en ${new Set(D.clases.map((c) => c.programa)).size} programas`); // ---- conocimiento y contacto ----------------------------------------- for (const g of D.guiones) { await cn.execute( 'INSERT INTO guiones (categoria, titulo, cuerpo, canal, orden) VALUES (?,?,?,?,?)', [g.categoria, g.titulo, g.cuerpo, g.canal, g.orden], ); } for (const a of D.kbArticulos) { await cn.execute( 'INSERT INTO kb_articulos (seccion, pregunta, respuesta, palabras_clave, orden) VALUES (?,?,?,?,?)', [a.seccion, a.pregunta, a.respuesta, a.palabras_clave, a.orden], ); } for (const i of D.institucional) { await cn.execute( 'INSERT INTO institucional (grupo, clave, etiqueta, valor, orden) VALUES (?,?,?,?,?)', [i.grupo, i.clave, i.etiqueta, i.valor, i.orden], ); } for (const d of D.discrepancias) { await cn.execute( 'INSERT INTO discrepancias (titulo, detalle, ambito, prioridad, estado, responsable, orden) VALUES (?,?,?,?,?,?,?)', [d.titulo, d.detalle, d.ambito, d.prioridad, d.estado, d.responsable, d.orden], ); } log('conocimiento y contacto', `${D.guiones.length} mensajes · ${D.kbArticulos.length} preguntas · ${D.institucional.length} datos · ${D.discrepancias.length} por confirmar`); await cn.execute( "INSERT INTO bitacora (actor, rol, accion, entidad, detalle) VALUES ('sistema','','db.siembra','base','Base reconstruida desde la base de conocimiento INSTA')", ); // ---- verificación ---------------------------------------------------- const [tablas] = await cn.query( 'SELECT TABLE_NAME AS t FROM information_schema.tables WHERE table_schema = ? ORDER BY t', [config.db.database], ); let total = 0; const vacias = []; for (const { t } of tablas) { const [[{ n }]] = await cn.query(`SELECT COUNT(*) AS n FROM \`${t}\``); total += n; if (n === 0) vacias.push(t); } console.log(`\n ✔ ${tablas.length} tablas · ${total} filas`); console.log(` ✔ vacías por diseño: ${vacias.join(', ') || 'ninguna'}`); console.log('\n Accesos demo: director@insta.demo · administracion@insta.demo · asesor@insta.demo\n'); await cn.end(); } main().catch((err) => { console.error('\n✖ La siembra falló:', err.message); process.exit(1); });