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]>
43 lines
944 B
JavaScript
43 lines
944 B
JavaScript
import mysql from 'mysql2/promise';
|
|
import { config } from './config.mjs';
|
|
|
|
let pool;
|
|
|
|
export function getPool() {
|
|
if (!pool) {
|
|
pool = mysql.createPool({
|
|
...config.db,
|
|
waitForConnections: true,
|
|
connectionLimit: 10,
|
|
queueLimit: 0,
|
|
charset: 'utf8mb4',
|
|
timezone: 'Z',
|
|
dateStrings: ['DATE'],
|
|
});
|
|
}
|
|
return pool;
|
|
}
|
|
|
|
/** Consulta que devuelve filas. */
|
|
export async function q(sql, params = []) {
|
|
const [rows] = await getPool().execute(sql, params);
|
|
return rows;
|
|
}
|
|
|
|
/** Consulta que devuelve la primera fila o null. */
|
|
export async function q1(sql, params = []) {
|
|
const rows = await q(sql, params);
|
|
return rows[0] ?? null;
|
|
}
|
|
|
|
/** INSERT/UPDATE/DELETE; devuelve el ResultSetHeader. */
|
|
export async function exec(sql, params = []) {
|
|
const [res] = await getPool().execute(sql, params);
|
|
return res;
|
|
}
|
|
|
|
export async function cerrar() {
|
|
if (pool) await pool.end();
|
|
pool = undefined;
|
|
}
|