Files
insta-portal/db/schema.sql
T
Uriel JarethandClaude Opus 5 0b9c9c1dc2 Portal interno INSTA: base de conocimiento con Magic Login y permisos por rol
Base de conocimiento para el personal del Instituto Santiago Tapia: oferta
educativa (en tarjetas y en tabla), requisitos y titulacion, becas y fechas
de inscripcion, links de clases de Google Meet, preguntas frecuentes,
mensajes listos y datos del instituto.

Acceso con Magic Login: enlace de un solo uso, 15 minutos de vigencia, y en
la base solo se guarda el SHA-256 del token. El canje es atomico, asi que dos
canjes simultaneos del mismo enlace no pueden crear dos sesiones.

Tres roles con 19 permisos. El catalogo lo edita direccion; los links de
clases, direccion y administracion; el resto del equipo consulta. El menu se
pinta con lo que el servidor autoriza y cada endpoint exige la misma clave,
asi que no hay pantallas alcanzables escribiendo la direccion a mano.

Toda edicion pasa por una lista blanca de campos validados y queda en la
bitacora. Un precio vacio no se guarda como cero, un link que no es una
direccion se rechaza, y no se puede marcar una validez oficial como
confirmada sin numero de acuerdo.

Pensado para leerse comodo: texto de 17 px, contraste AA, areas de toque de
44 px, tema claro y bloques plegados en telefono.

La oferta, los costos, las becas, los requisitos y el contacto son los reales
del instituto, tomados del CSV de oferta educativa y de la base de
conocimiento. Los usuarios de acceso y los links de Meet son de demostracion.

Verificado: 103 comprobaciones de API y 51 pruebas en WebKit escritorio y
iPhone 13.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-29 23:20:34 -06:00

298 lines
13 KiB
SQL

-- =====================================================================
-- Portal Interno INSTA — esquema
-- Instituto Santiago Tapia · Morelia, Michoacán
-- MySQL 8.0 · utf8mb4
-- =====================================================================
-- El portal es una base de conocimiento: oferta educativa, requisitos,
-- becas y fechas, links de clases, preguntas frecuentes y contacto.
-- El catálogo lo edita dirección; los links de clases, dirección y
-- administración. Toda edición queda en la bitácora.
--
-- El esquema se reconstruye completo en cada `npm run db:reset`.
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS bitacora;
DROP TABLE IF EXISTS sesiones;
DROP TABLE IF EXISTS magic_tokens;
DROP TABLE IF EXISTS clases;
DROP TABLE IF EXISTS programas_modalidades;
DROP TABLE IF EXISTS requisitos;
DROP TABLE IF EXISTS programas;
DROP TABLE IF EXISTS modalidades;
DROP TABLE IF EXISTS niveles;
DROP TABLE IF EXISTS promociones;
DROP TABLE IF EXISTS guiones;
DROP TABLE IF EXISTS kb_articulos;
DROP TABLE IF EXISTS titulacion_opciones;
DROP TABLE IF EXISTS institucional;
DROP TABLE IF EXISTS discrepancias;
DROP TABLE IF EXISTS roles_permisos;
DROP TABLE IF EXISTS permisos;
DROP TABLE IF EXISTS usuarios;
DROP TABLE IF EXISTS roles;
-- Tablas de la versión anterior del portal, ya retiradas.
DROP TABLE IF EXISTS prospectos_etiquetas;
DROP TABLE IF EXISTS prospectos;
DROP TABLE IF EXISTS pendientes;
DROP TABLE IF EXISTS cotizaciones;
DROP TABLE IF EXISTS metricas_crm;
DROP TABLE IF EXISTS etiquetas;
SET FOREIGN_KEY_CHECKS = 1;
-- ---------------------------------------------------------------------
-- 1. Identidad, roles y permisos
-- ---------------------------------------------------------------------
CREATE TABLE roles (
id INT AUTO_INCREMENT PRIMARY KEY,
clave VARCHAR(32) NOT NULL UNIQUE,
nombre VARCHAR(64) NOT NULL,
nivel TINYINT NOT NULL COMMENT '3 dirección · 2 administración · 1 empleado',
color CHAR(7) NOT NULL DEFAULT '#4275B7',
descripcion VARCHAR(255) NOT NULL DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE permisos (
id INT AUTO_INCREMENT PRIMARY KEY,
clave VARCHAR(48) NOT NULL UNIQUE COMMENT 'seccion.accion',
seccion VARCHAR(32) NOT NULL,
descripcion VARCHAR(255) NOT NULL DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE roles_permisos (
rol_id INT NOT NULL,
permiso_id INT NOT NULL,
PRIMARY KEY (rol_id, permiso_id),
CONSTRAINT fk_rp_rol FOREIGN KEY (rol_id) REFERENCES roles(id) ON DELETE CASCADE,
CONSTRAINT fk_rp_permiso FOREIGN KEY (permiso_id) REFERENCES permisos(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE usuarios (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(96) NOT NULL,
email VARCHAR(160) NOT NULL UNIQUE,
puesto VARCHAR(96) NOT NULL DEFAULT '',
rol_id INT NOT NULL,
iniciales CHAR(2) NOT NULL DEFAULT '??',
extension VARCHAR(16) NOT NULL DEFAULT '',
activo TINYINT(1) NOT NULL DEFAULT 1,
es_demo TINYINT(1) NOT NULL DEFAULT 1 COMMENT 'visible en el acceso rápido de la demo',
ultimo_acceso DATETIME NULL,
creado_en DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_usuario_rol FOREIGN KEY (rol_id) REFERENCES roles(id),
INDEX idx_usuarios_rol (rol_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Magic Login: el token nunca se guarda en claro, solo su SHA-256.
CREATE TABLE magic_tokens (
id INT AUTO_INCREMENT PRIMARY KEY,
usuario_id INT NOT NULL,
token_hash CHAR(64) NOT NULL UNIQUE,
proposito VARCHAR(32) NOT NULL DEFAULT 'login',
creado_en DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
expira_en DATETIME NOT NULL,
usado_en DATETIME NULL,
ip_origen VARCHAR(64) NOT NULL DEFAULT '',
CONSTRAINT fk_token_usuario FOREIGN KEY (usuario_id) REFERENCES usuarios(id) ON DELETE CASCADE,
INDEX idx_tokens_usuario (usuario_id),
INDEX idx_tokens_expira (expira_en)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE sesiones (
id CHAR(64) PRIMARY KEY COMMENT 'SHA-256 del id que viaja en la cookie',
usuario_id INT NOT NULL,
creada_en DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
expira_en DATETIME NOT NULL,
user_agent VARCHAR(255) NOT NULL DEFAULT '',
ip_origen VARCHAR(64) NOT NULL DEFAULT '',
CONSTRAINT fk_sesion_usuario FOREIGN KEY (usuario_id) REFERENCES usuarios(id) ON DELETE CASCADE,
INDEX idx_sesiones_usuario (usuario_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE bitacora (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
usuario_id INT NULL,
actor VARCHAR(96) NOT NULL DEFAULT 'sistema',
rol VARCHAR(32) NOT NULL DEFAULT '',
accion VARCHAR(64) NOT NULL,
entidad VARCHAR(48) NOT NULL DEFAULT '',
entidad_id VARCHAR(48) NOT NULL DEFAULT '',
resultado VARCHAR(16) NOT NULL DEFAULT 'ok' COMMENT 'ok · denegado · error',
detalle VARCHAR(500) NOT NULL DEFAULT '',
ip_origen VARCHAR(64) NOT NULL DEFAULT '',
creado_en DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
CONSTRAINT fk_bitacora_usuario FOREIGN KEY (usuario_id) REFERENCES usuarios(id) ON DELETE SET NULL,
INDEX idx_bitacora_creado (creado_en),
INDEX idx_bitacora_accion (accion)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ---------------------------------------------------------------------
-- 2. Oferta educativa
-- ---------------------------------------------------------------------
CREATE TABLE niveles (
id INT AUTO_INCREMENT PRIMARY KEY,
clave VARCHAR(32) NOT NULL UNIQUE,
nombre VARCHAR(64) NOT NULL,
orden TINYINT NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE modalidades (
id INT AUTO_INCREMENT PRIMARY KEY,
clave VARCHAR(32) NOT NULL UNIQUE,
nombre VARCHAR(64) NOT NULL,
color CHAR(7) NOT NULL DEFAULT '#5E35B1',
detalle VARCHAR(200) NOT NULL DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE programas (
id INT AUTO_INCREMENT PRIMARY KEY,
nivel_id INT NOT NULL,
clave VARCHAR(48) NOT NULL UNIQUE,
nombre VARCHAR(120) NOT NULL,
nombre_corto VARCHAR(48) NOT NULL,
duracion VARCHAR(48) NOT NULL,
fecha_inicio VARCHAR(32) NOT NULL,
inscripcion_lista DECIMAL(10,2) NOT NULL,
inscripcion_beca DECIMAL(10,2) NOT NULL,
mensualidad_lista DECIMAL(10,2) NOT NULL,
mensualidad_beca DECIMAL(10,2) NOT NULL,
ciclo_semanas SMALLINT NULL,
rvoe_acuerdo VARCHAR(48) NOT NULL DEFAULT '',
rvoe_fecha DATE NULL,
rvoe_autoridad VARCHAR(120) NOT NULL DEFAULT '',
rvoe_estado VARCHAR(24) NOT NULL DEFAULT 'confirmado' COMMENT 'confirmado · revisar · por-confirmar',
rvoe_nota VARCHAR(500) NOT NULL DEFAULT '',
beneficios VARCHAR(500) NOT NULL DEFAULT '',
color CHAR(7) NOT NULL DEFAULT '#17A171',
orden TINYINT NOT NULL DEFAULT 0,
activo TINYINT(1) NOT NULL DEFAULT 1,
actualizado_por INT NULL,
actualizado_en DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_programa_nivel FOREIGN KEY (nivel_id) REFERENCES niveles(id),
CONSTRAINT fk_programa_usuario FOREIGN KEY (actualizado_por) REFERENCES usuarios(id) ON DELETE SET NULL,
INDEX idx_programas_nivel (nivel_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE programas_modalidades (
id INT AUTO_INCREMENT PRIMARY KEY,
programa_id INT NOT NULL,
modalidad_id INT NOT NULL,
dias VARCHAR(64) NOT NULL DEFAULT '',
hora_inicio VARCHAR(8) NOT NULL DEFAULT '',
hora_fin VARCHAR(8) NOT NULL DEFAULT '',
nota VARCHAR(160) NOT NULL DEFAULT '',
CONSTRAINT fk_pm_programa FOREIGN KEY (programa_id) REFERENCES programas(id) ON DELETE CASCADE,
CONSTRAINT fk_pm_modalidad FOREIGN KEY (modalidad_id) REFERENCES modalidades(id) ON DELETE CASCADE,
INDEX idx_pm_programa (programa_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE requisitos (
id INT AUTO_INCREMENT PRIMARY KEY,
nivel_id INT NOT NULL,
texto VARCHAR(160) NOT NULL,
nota VARCHAR(200) NOT NULL DEFAULT '',
orden TINYINT NOT NULL DEFAULT 0,
CONSTRAINT fk_requisito_nivel FOREIGN KEY (nivel_id) REFERENCES niveles(id) ON DELETE CASCADE,
INDEX idx_requisitos_nivel (nivel_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE titulacion_opciones (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(64) NOT NULL,
descripcion VARCHAR(255) NOT NULL DEFAULT '',
orden TINYINT NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ---------------------------------------------------------------------
-- 3. Becas, promociones y fechas de inscripción
-- ---------------------------------------------------------------------
CREATE TABLE promociones (
id INT AUTO_INCREMENT PRIMARY KEY,
clave VARCHAR(48) NOT NULL UNIQUE,
nombre VARCHAR(120) NOT NULL,
resumen VARCHAR(200) NOT NULL DEFAULT '' COMMENT 'la frase que se le dice al interesado',
aplica_a VARCHAR(24) NOT NULL COMMENT 'inscripcion · mensualidad · ambos',
vigencia VARCHAR(120) NOT NULL DEFAULT '',
acumulable TINYINT(1) NOT NULL DEFAULT 0,
condiciones VARCHAR(900) NOT NULL DEFAULT '',
activo TINYINT(1) NOT NULL DEFAULT 1,
orden TINYINT NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ---------------------------------------------------------------------
-- 4. Links de clases (Google Meet)
-- ---------------------------------------------------------------------
CREATE TABLE clases (
id INT AUTO_INCREMENT PRIMARY KEY,
programa_id INT NOT NULL,
materia VARCHAR(120) NOT NULL,
docente VARCHAR(96) NOT NULL DEFAULT '',
grupo VARCHAR(48) NOT NULL DEFAULT '' COMMENT 'generación o grupo',
dias VARCHAR(64) NOT NULL DEFAULT '',
hora_inicio VARCHAR(8) NOT NULL DEFAULT '',
hora_fin VARCHAR(8) NOT NULL DEFAULT '',
enlace VARCHAR(500) NOT NULL DEFAULT '',
notas VARCHAR(300) NOT NULL DEFAULT '',
activo TINYINT(1) NOT NULL DEFAULT 1,
orden SMALLINT NOT NULL DEFAULT 0,
actualizado_por INT NULL,
actualizado_en DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
creado_en DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_clase_programa FOREIGN KEY (programa_id) REFERENCES programas(id) ON DELETE CASCADE,
CONSTRAINT fk_clase_usuario FOREIGN KEY (actualizado_por) REFERENCES usuarios(id) ON DELETE SET NULL,
INDEX idx_clases_programa (programa_id),
INDEX idx_clases_materia (materia)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ---------------------------------------------------------------------
-- 5. Conocimiento, contacto y datos por confirmar
-- ---------------------------------------------------------------------
CREATE TABLE guiones (
id INT AUTO_INCREMENT PRIMARY KEY,
categoria VARCHAR(48) NOT NULL,
titulo VARCHAR(160) NOT NULL,
cuerpo TEXT NOT NULL,
canal VARCHAR(24) NOT NULL DEFAULT 'whatsapp',
orden SMALLINT NOT NULL DEFAULT 0,
INDEX idx_guiones_categoria (categoria)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE kb_articulos (
id INT AUTO_INCREMENT PRIMARY KEY,
seccion VARCHAR(48) NOT NULL,
pregunta VARCHAR(255) NOT NULL,
respuesta TEXT NOT NULL,
palabras_clave VARCHAR(255) NOT NULL DEFAULT '',
fuente VARCHAR(120) NOT NULL DEFAULT 'Base de conocimiento INSTA',
orden SMALLINT NOT NULL DEFAULT 0,
INDEX idx_kb_seccion (seccion)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE institucional (
id INT AUTO_INCREMENT PRIMARY KEY,
grupo VARCHAR(32) NOT NULL,
clave VARCHAR(48) NOT NULL UNIQUE,
etiqueta VARCHAR(96) NOT NULL,
valor TEXT NOT NULL,
orden SMALLINT NOT NULL DEFAULT 0,
INDEX idx_institucional_grupo (grupo)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE discrepancias (
id INT AUTO_INCREMENT PRIMARY KEY,
titulo VARCHAR(180) NOT NULL,
detalle VARCHAR(900) NOT NULL DEFAULT '',
ambito VARCHAR(48) NOT NULL DEFAULT 'rvoe',
prioridad VARCHAR(16) NOT NULL DEFAULT 'media',
estado VARCHAR(24) NOT NULL DEFAULT 'abierto' COMMENT 'abierto · en-proceso · aclarado',
responsable VARCHAR(96) NOT NULL DEFAULT 'Dirección',
orden SMALLINT NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;