Function bodies 67 total
safeRoute function · javascript · L60-L75 (16 LOC)backend/index.js
function safeRoute(path, routeModule, routeName) {
try {
if (typeof routeModule === 'function') {
app.use(path, routeModule);
} else {
app.use(path, require(routeModule));
}
logger.info(`✅ Ruta cargada: ${routeName || path}`);
} catch (err) {
logger.error(`❌ Error cargando ruta ${routeName || path}: ${err.message}`, { stack: err.stack });
// Mount a fallback that returns 503 for this path
app.use(path, (req, res) => {
res.status(503).json({ error: `Módulo ${routeName || path} no disponible`, detail: err.message });
});
}
}seed function · javascript · L4-L176 (173 LOC)backend/scripts/debug/advanced_seed.js
async function seed() {
console.log('🚀 Iniciando Super Seed de datos...');
try {
// 1. Limpiar algunas tablas para evitar duplicados si se corre varias veces
// (Opcional, pero útil para pruebas limpias)
// await db.query('SET FOREIGN_KEY_CHECKS = 0');
// await db.query('TRUNCATE TABLE asistencias');
// await db.query('TRUNCATE TABLE documentos');
// await db.query('TRUNCATE TABLE trabajadores');
// await db.query('SET FOREIGN_KEY_CHECKS = 1');
// 2. Cargos
const cargos = [
'Administrador de Obra', 'Jefe de Terreno', 'Prevencionista de Riesgos',
'Capataz', 'Maestro Mayor', 'Maestro Primera', 'Ayudante', 'Jornal',
'Operador de Grúa', 'Bodeguero'
];
for (const c of cargos) {
await db.query('INSERT IGNORE INTO cargos (nombre) VALUES (?)', [c]);
}
const [cargoRows] = await db.query('SELECT id FROM cargos');
const cargoIds run function · javascript · L5-L26 (22 LOC)backend/scripts/debug/execute_sql.js
async function run() {
try {
const sql = fs.readFileSync('fix_and_import_miguel.sql', 'utf-8');
console.log('SQL file read successfully. Connecting to DB...');
const connection = await mysql.createConnection({
host: process.env.DB_HOST || 'lols.cl',
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: process.env.DB_PORT || 3306,
multipleStatements: true
});
console.log('Connected. Executing script...');
const [results] = await connection.query(sql);
console.log('Script execution complete. Results:', results ? results.length + ' statements executed' : 'No result array');
await connection.end();
} catch (err) {
console.error('Error executing SQL:', err);
}
}sync function · javascript · L4-L19 (16 LOC)backend/scripts/debug/force_sync_feriados.js
async function sync() {
try {
console.log('🔄 Sincronizando feriados 2025...');
const result = await feriadosService.syncNacionalHolidays(2025);
console.log(`✅ Sincronización exitosa: ${result.count} feriados cargados.`);
console.log('🔄 Sincronizando feriados 2024 (por si acaso)...');
const result2 = await feriadosService.syncNacionalHolidays(2024);
console.log(`✅ Sincronización exitosa: ${result2.count} feriados cargados.`);
process.exit(0);
} catch (err) {
console.error('❌ Error sincronizando:', err);
process.exit(1);
}
}inspect function · javascript · L3-L22 (20 LOC)backend/scripts/debug/inspect_schema.js
async function inspect() {
try {
console.log('--- TABLA asistencias ---');
const [colsA] = await db.query('SHOW COLUMNS FROM asistencias');
console.log(colsA.map(c => c.Field).join(', '));
console.log('\n--- TABLA trabajadores ---');
const [colsT] = await db.query('SHOW COLUMNS FROM trabajadores');
console.log(colsT.map(c => c.Field).join(', '));
console.log('\n--- TABLA estados_asistencia ---');
const [colsE] = await db.query('SHOW COLUMNS FROM estados_asistencia');
console.log(colsE.map(c => c.Field).join(', '));
} catch (err) {
console.error('ERROR:', err.message);
} finally {
process.exit();
}
}listUsers function · javascript · L8-L24 (17 LOC)backend/scripts/debug/list_users.js
async function listUsers() {
const connection = await mysql.createConnection({
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || 'sgdl'
});
try {
const [rows] = await connection.execute('SELECT id, email, nombre, activo FROM usuarios');
console.log('USERS_JSON:' + JSON.stringify(rows, null, 2));
} catch (err) {
console.error('Error listing users:', err);
} finally {
await connection.end();
}
}runOpt function · javascript · L5-L28 (24 LOC)backend/scripts/debug/run_optimizations.js
async function runOpt() {
try {
console.log('🚀 Running 010_optimizacion_db.sql...');
const sqlPath = path.join(__dirname, 'db/migrations/010_optimizacion_db.sql');
const sqlStr = fs.readFileSync(sqlPath, 'utf8');
const statements = sqlStr.split(';')
.map(s => s.trim())
.filter(s => s.length > 0 && !s.startsWith('--'));
for (let s of statements) {
// Eliminar comentarios en la misma línea y otras cosas que no sean código SQL puro si es necesario, pero suele bastar
if (s && s.toLowerCase().indexOf('alter') >= 0 || s.toLowerCase().indexOf('create') >= 0 || s.toLowerCase().indexOf('update') >= 0) {
console.log('Executing:', s.substring(0, 50) + '...');
await db.query(s);
}
}
console.log('✅ Database optimization and normalization complete.');
process.exit(0);
} catch (err) {
console.error('❌ Error applying optimization:Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
api function · javascript · L12-L39 (28 LOC)backend/scripts/debug/test-api.js
function api(method, path, body = null) {
return new Promise((resolve, reject) => {
const url = new URL(path, BASE);
const data = body ? JSON.stringify(body) : null;
const options = {
hostname: url.hostname,
port: url.port,
path: url.pathname + url.search,
method,
headers: {
'Content-Type': 'application/json',
...(TOKEN ? { Authorization: `Bearer ${TOKEN}` } : {}),
...(data ? { 'Content-Length': Buffer.byteLength(data) } : {})
}
};
const req = http.request(options, res => {
let body = '';
res.on('data', c => (body += c));
res.on('end', () => {
try { resolve({ status: res.statusCode, data: JSON.parse(body) }); }
catch { resolve({ status: res.statusCode, data: body }); }
});
});
req.on('error', reject);
if (data) req.write(dlog function · javascript · L41-L43 (3 LOC)backend/scripts/debug/test-api.js
function log(icon, label, detail = '') {
console.log(` ${icon} ${label}${detail ? ` → ${detail}` : ''}`);
}test function · javascript · L45-L54 (10 LOC)backend/scripts/debug/test-api.js
async function test(name, fn) {
try {
await fn();
log('✅', name);
return true;
} catch (err) {
log('❌', name, err.message);
return false;
}
}run function · javascript · L58-L329 (272 LOC)backend/scripts/debug/test-api.js
async function run() {
let passed = 0, failed = 0;
const track = (ok) => ok ? passed++ : failed++;
console.log('\n╔══════════════════════════════════════════════╗');
console.log('║ SGDL - Test de Integración API ║');
console.log('╚══════════════════════════════════════════════╝\n');
// ── 1. AUTH ──
console.log('📋 1. AUTENTICACIÓN');
track(await test('Login con credenciales válidas', async () => {
const res = await api('POST', '/api/auth/login', { email: '[email protected]', password: 'admin' });
assert(res.status === 200, `Status ${res.status}: ${JSON.stringify(res.data)}`);
assert(res.data.token, 'No se recibió token');
TOKEN = res.data.token;
}));
track(await test('Login con credenciales inválidas → 401', async () => {
const res = await api('POST', '/api/auth/login', { email: '[email protected]', password: '12345' });
assert(res.status === 401, `Esperaba 401, recibió ${res.status}`)main function · javascript · L31-L84 (54 LOC)backend/scripts/fix_prod_migrations.js
async function main() {
const conn = await mysql.createConnection({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: process.env.DB_PORT || 3306,
multipleStatements: true,
charset: 'utf8mb4',
});
console.log('🔧 Fix producción: ejecutando migraciones faltantes de inventario\n');
for (const file of MISSING) {
const filePath = path.join(MIGRATIONS_DIR, file);
if (!fs.existsSync(filePath)) {
console.error(`❌ Archivo no encontrado: ${file}`);
process.exit(1);
}
const sql = fs.readFileSync(filePath, 'utf8').trim();
if (!sql) continue;
console.log(`🔹 Ejecutando ${file}...`);
const start = Date.now();
try {
await conn.query(sql);
const ms = Date.now() - start;
// Actualizar schema_migrations con el tiempo real de ejecuciómain function · javascript · L18-L166 (149 LOC)backend/scripts/import_inventario.js
async function main() {
let conn;
try {
console.log('🔄 Iniciando importación de Inventario...');
conn = await db.getConnection();
await conn.beginTransaction();
// 1. Obtener el super admin para 'registrado_por'
const [users] = await conn.query('SELECT id FROM usuarios WHERE rol_id = 1 LIMIT 1');
const superAdminId = users.length > 0 ? users[0].id : 1;
// 2. Mapear Obras existentes
const [obrasDb] = await conn.query('SELECT id, nombre FROM obras');
const getObraId = async (obraName) => {
if (obraName.includes('LOLS_GENERAL')) return null; // Oficina central o bodega central
const matchedObra = obrasDb.find(o =>
limpiarString(o.nombre).includes(limpiarString(obraName)) ||
limpiarString(obraName).includes(limpiarString(o.nombre))
);
if (matchedObra) return matchedObra.id;
// Si no existe, crear la olistMigrationFiles function · javascript · L56-L60 (5 LOC)backend/scripts/migrate.js
function listMigrationFiles() {
return fs.readdirSync(MIGRATIONS_DIR)
.filter(f => /^\d{3}_.+\.sql$/i.test(f)) // solo NNN_xxx.sql
.sort(); // 001, 002, ..., 099 alfabético
}getMigrationNumber function · javascript · L62-L65 (4 LOC)backend/scripts/migrate.js
function getMigrationNumber(filename) {
const match = filename.match(/^(\d{3})_/);
return match ? match[1] : null;
}About: code-quality intelligence by Repobility · https://repobility.com
ensureSchemaMigrationsTable function · javascript · L67-L75 (9 LOC)backend/scripts/migrate.js
async function ensureSchemaMigrationsTable(conn) {
await conn.query(`
CREATE TABLE IF NOT EXISTS schema_migrations (
name VARCHAR(255) NOT NULL PRIMARY KEY,
applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
duration_ms INT DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`);
}isExistingDatabase function · javascript · L77-L85 (9 LOC)backend/scripts/migrate.js
async function isExistingDatabase(conn) {
// Si la tabla `usuarios` ya existe, asumimos que es una BD en uso
const [rows] = await conn.query(`
SELECT COUNT(*) AS n
FROM information_schema.tables
WHERE table_schema = DATABASE() AND table_name = 'usuarios'
`);
return rows[0].n > 0;
}getAppliedMigrations function · javascript · L87-L90 (4 LOC)backend/scripts/migrate.js
async function getAppliedMigrations(conn) {
const [rows] = await conn.query('SELECT name FROM schema_migrations');
return new Set(rows.map(r => r.name));
}bootstrapExistingDatabase function · javascript · L92-L107 (16 LOC)backend/scripts/migrate.js
async function bootstrapExistingDatabase(conn, files) {
const toMark = files.filter(f => {
const num = getMigrationNumber(f);
return num && num <= BOOTSTRAP_CUTOFF;
});
if (toMark.length === 0) return;
log.warn(`BD existente detectada. Marcando ${toMark.length} migraciones como aplicadas sin ejecutarlas (bootstrap).`);
for (const f of toMark) {
await conn.query(
'INSERT IGNORE INTO schema_migrations (name, duration_ms) VALUES (?, 0)',
[f]
);
}
log.ok(`Bootstrap completo. Solo se ejecutarán migraciones posteriores a ${BOOTSTRAP_CUTOFF}.`);
}applyMigration function · javascript · L109-L139 (31 LOC)backend/scripts/migrate.js
async function applyMigration(conn, file) {
const full = path.join(MIGRATIONS_DIR, file);
const sql = fs.readFileSync(full, 'utf8');
// Omitir archivos vacíos o solo con comentarios
const meaningful = sql
.split('\n')
.filter(l => l.trim() && !l.trim().startsWith('--'))
.join('\n')
.trim();
if (!meaningful) {
log.warn(`${file} está vacía/sin SQL — marcando como aplicada sin ejecutar.`);
await conn.query('INSERT INTO schema_migrations (name, duration_ms) VALUES (?, 0)', [file]);
return;
}
log.step(`Aplicando ${file}…`);
const t0 = Date.now();
try {
await conn.query(sql);
const duration = Date.now() - t0;
await conn.query(
'INSERT INTO schema_migrations (name, duration_ms) VALUES (?, ?)',
[file, duration]
);
log.ok(`${file} aplicada (${duration}ms)`);
} catch (err) {
log.err(`Falló ${file}: ${err.message}`);
throrunMaintenanceTasks function · javascript · L141-L170 (30 LOC)backend/scripts/migrate.js
async function runMaintenanceTasks(conn) {
log.step('Ejecutando tareas de mantenimiento idempotentes…');
// a) columna fecha_desvinculacion en trabajadores
await conn.query(`ALTER TABLE trabajadores ADD COLUMN IF NOT EXISTS fecha_desvinculacion DATE NULL DEFAULT NULL`);
// b) versioning en roles
await conn.query(`ALTER TABLE roles ADD COLUMN IF NOT EXISTS version INT NOT NULL DEFAULT 1`);
// c) permiso trabajadores.depurar (ex purgar)
await conn.query(`
INSERT IGNORE INTO permisos_catalogo (clave, modulo, nombre, descripcion, orden)
VALUES ('trabajadores.depurar', 'Trabajadores', 'Depurar Trabajador',
'Eliminar permanentemente trabajadores finiquitados', 6)
`);
await conn.query(`UPDATE permisos_rol_v2 SET permiso_clave = 'trabajadores.depurar' WHERE permiso_clave = 'trabajadores.purgar'`);
await conn.query(`UPDATE permisos_usuario_override SET permiso_clave = 'trabajadores.depurar' WHERE permiso_clave = 'trabajarun function · javascript · L4-L21 (18 LOC)backend/scripts/migrations/add_fecha_desvinculacion.js
async function run() {
try {
console.log("Adding fecha_desvinculacion to trabajadores...");
await db.query(`
ALTER TABLE trabajadores
ADD COLUMN fecha_desvinculacion DATE NULL DEFAULT NULL AFTER activo;
`);
console.log("Column added or already exists.");
} catch (e) {
if (e.code === 'ER_DUP_FIELDNAME') {
console.log("Column already exists.");
} else {
console.error(e);
}
} finally {
process.exit();
}
}migrate function · javascript · L4-L201 (198 LOC)backend/scripts/migrations/migrate_granular_permissions.js
async function migrate() {
console.log('🚀 Iniciando migración de permisos granulares...');
const connection = await db.getConnection();
try {
await connection.beginTransaction();
// 1. Crear tabla permisos_catalogo
console.log('--- Creando tabla permisos_catalogo ---');
await connection.query(`
CREATE TABLE IF NOT EXISTS permisos_catalogo (
id INT AUTO_INCREMENT PRIMARY KEY,
clave VARCHAR(100) NOT NULL UNIQUE,
modulo VARCHAR(50) NOT NULL,
nombre VARCHAR(100) NOT NULL,
descripcion VARCHAR(255) DEFAULT NULL,
orden INT DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`);
// 2. Crear tabla permisos_rol_v2
console.log('--- Creando tabla permisos_rol_v2 ---');
await connection.query(`
CREATE TABLE IF NOT EXISTS permisos_rol_v2 (
rol_id INT NOT NULL,
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
migrate function · javascript · L10-L48 (39 LOC)backend/scripts/migrations/migrate_missing_tables.js
async function migrate() {
console.log('🔄 Creating missing tables...\n');
// 1. horarios_obra table
await db.query(`
CREATE TABLE IF NOT EXISTS horarios_obra (
id INT AUTO_INCREMENT PRIMARY KEY,
obra_id INT NOT NULL,
dia_semana INT NOT NULL COMMENT '0=Lunes, 6=Domingo',
entrada VARCHAR(5) DEFAULT '08:00',
salida VARCHAR(5) DEFAULT '18:00',
inicio_colacion VARCHAR(5) DEFAULT NULL,
fin_colacion VARCHAR(5) DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY unique_obra_dia (obra_id, dia_semana),
FOREIGN KEY (obra_id) REFERENCES obras(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`);
console.log('✅ Tabla horarios_obra creada/verificada');
// 2. plantillas_correo table
await db.query(`
CREATE TABLE IF NOT EXISTS plantillas_correo (
id INT AUTO_INCREMENT PRIMARY KEY,
migrate function · javascript · L3-L36 (34 LOC)backend/scripts/migrations/migrate_obras_null.js
async function migrate() {
const connection = await mysql.createConnection({
host: 'localhost',
user: 'root',
password: '', // Empty password as per .env
database: 'sgdl' // As per .env
});
try {
console.log('Running migration: obras.empresa_id NULLable...');
// Make empresa_id nullable
await connection.query('ALTER TABLE obras MODIFY COLUMN empresa_id INT NULL');
console.log('✅ Migration successful: obras.empresa_id is now NULLable.');
} catch (err) {
if (err.code === 'ER_BAD_DB_ERROR') {
// Maybe it's 'boveda_lols'?
console.error('❌ Check DB name. Trying "boveda_lols"...');
try {
await connection.changeUser({ database: 'boveda_lols' });
await connection.query('ALTER TABLE obras MODIFY COLUMN empresa_id INT NULL');
console.log('✅ Migration success on boveda_lols');
return;
} catch (e) migrate function · javascript · L4-L55 (52 LOC)backend/scripts/migrations/migrate_states_v10.js
async function migrate() {
try {
console.log('--- Iniciando Migración de Estados v10 ---');
// 1. Obtener IDs de Atrasos y JI
const [atrasos] = await db.query('SELECT id, nombre, codigo FROM estados_asistencia WHERE codigo = "AT" OR nombre LIKE "%atraso%"');
const [ji] = await db.query('SELECT id FROM estados_asistencia WHERE codigo = "JI"');
if (ji.length === 0) {
console.error('Error: No se encontró el estado JI (Jornada Incompleta).');
process.exit(1);
}
const jiId = ji[0].id;
for (const a of atrasos) {
console.log(`Migrando registros de "${a.nombre}" (${a.codigo}) -> JI...`);
const [updateRes] = await db.query('UPDATE asistencias SET estado_id = ? WHERE estado_id = ?', [jiId, a.id]);
console.log(`- ${updateRes.affectedRows} registros actualizados.`);
await db.query('UPDATE estados_asistencia SET activo = 0 WHERE id = ?', [a.runMigration function · javascript · L6-L33 (28 LOC)backend/scripts/migrations/run_migration.js
async function runMigration() {
const connection = await mysql.createConnection({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: process.env.DB_PORT || 3306,
multipleStatements: true
});
try {
const sql = fs.readFileSync(path.join(__dirname, 'db/migrations/012_periodos_ausencia.sql'), 'utf8');
console.log('Ejecutando migración 012_periodos_ausencia.sql...');
await connection.query(sql);
console.log('✅ Migración ejecutada exitosamente');
// Verify
const [tables] = await connection.query("SHOW TABLES LIKE 'periodos_ausencia'");
console.log('Tabla periodos_ausencia existe:', tables.length > 0);
const [cols] = await connection.query("DESCRIBE periodos_ausencia");
console.table(cols.map(c => ({ Field: c.Field, Type: c.Type, Key: c.Key })));
} catch (err) {
runMigration function · javascript · L5-L24 (20 LOC)backend/scripts/migrations/temp_migrate_feriados.js
async function runMigration() {
try {
const sqlPath = path.join(__dirname, 'db', 'migrations', '014_feriados.sql');
const sql = fs.readFileSync(sqlPath, 'utf8');
// Split by semicolon (naive but works for simple migrations)
const commands = sql.split(';').filter(cmd => cmd.trim());
for (const cmd of commands) {
await db.query(cmd);
console.log('Executed:', cmd.substring(0, 50) + '...');
}
console.log('✅ Migración 014_feriados completada con éxito');
process.exit(0);
} catch (err) {
console.error('❌ Error ejecutando migración:', err.message);
process.exit(1);
}
}seed function · javascript · L4-L38 (35 LOC)backend/scripts/seed_document_types.js
async function seed() {
try {
console.log('🌱 Seeding document types...');
const types = [
{ nombre: 'Contrato de Trabajo', dias_vigencia: null, obligatorio: true },
{ nombre: 'Anexo de Contrato', dias_vigencia: null, obligatorio: false },
{ nombre: 'Cédula de Identidad', dias_vigencia: 3650, obligatorio: true },
{ nombre: 'Certificado de Antecedentes', dias_vigencia: 60, obligatorio: true },
{ nombre: 'Liquidación de Sueldo', dias_vigencia: null, obligatorio: false },
{ nombre: 'Finiquito', dias_vigencia: null, obligatorio: false },
{ nombre: 'Examen Preocupacional', dias_vigencia: 365, obligatorio: false },
{ nombre: 'Charla ODI', dias_vigencia: null, obligatorio: true }
];
for (const type of types) {
// Check if exists manually if UNIQUE name isn't set, otherwise rely on ID or just insert.
// Using INSERT IGNORE or checking firstvalidateEnv function · javascript · L13-L30 (18 LOC)backend/src/config/env-validator.js
function validateEnv() {
const missing = REQUIRED_ENV_VARS.filter(v => !process.env[v.key]);
if (missing.length > 0) {
console.error('\n⛔ ══════════════════════════════════════════════════');
console.error(' VARIABLES DE ENTORNO FALTANTES');
console.error('══════════════════════════════════════════════════\n');
missing.forEach(v => {
console.error(` ❌ ${v.key} — ${v.hint}`);
});
console.error('\n Revisa tu archivo .env o las variables del sistema.');
console.error('══════════════════════════════════════════════════\n');
if (process.env.NODE_ENV === 'test') {
throw new Error(`Variables de entorno faltantes: ${missing.map(v => v.key).join(', ')}`);
}
process.exit(1);
}
}ConfigHorariosService class · javascript · L3-L81 (79 LOC)backend/src/services/config-horarios.service.js
class ConfigHorariosService {
async getByObraId(obraId) {
if (obraId === 'ALL') {
const [rows] = await db.execute(`SELECT * FROM configuracion_horarios WHERE activo = TRUE`);
return rows;
}
const [rows] = await db.execute(
`SELECT * FROM configuracion_horarios
WHERE obra_id = ? AND activo = TRUE
ORDER BY
CASE dia_semana
WHEN 'lun' THEN 1
WHEN 'mar' THEN 2
WHEN 'mie' THEN 3
WHEN 'jue' THEN 4
WHEN 'vie' THEN 5
WHEN 'sab' THEN 6
END`,
[obraId]
);
if (rows.length === 0) {
const defaultDays = ['lun', 'mar', 'mie', 'jue', 'vie'];
const defaults = defaultDays.map(dia => ({
dia_semana: dia,
hora_entrada: '08:00:00',
hora_salida: '18:00:00',
hora_colAll rows above produced by Repobility · https://repobility.com
getByObraId method · javascript · L4-L40 (37 LOC)backend/src/services/config-horarios.service.js
async getByObraId(obraId) {
if (obraId === 'ALL') {
const [rows] = await db.execute(`SELECT * FROM configuracion_horarios WHERE activo = TRUE`);
return rows;
}
const [rows] = await db.execute(
`SELECT * FROM configuracion_horarios
WHERE obra_id = ? AND activo = TRUE
ORDER BY
CASE dia_semana
WHEN 'lun' THEN 1
WHEN 'mar' THEN 2
WHEN 'mie' THEN 3
WHEN 'jue' THEN 4
WHEN 'vie' THEN 5
WHEN 'sab' THEN 6
END`,
[obraId]
);
if (rows.length === 0) {
const defaultDays = ['lun', 'mar', 'mie', 'jue', 'vie'];
const defaults = defaultDays.map(dia => ({
dia_semana: dia,
hora_entrada: '08:00:00',
hora_salida: '18:00:00',
hora_colacion_inicio: '13:00:00',
updateWeeklyConfig method · javascript · L42-L80 (39 LOC)backend/src/services/config-horarios.service.js
async updateWeeklyConfig(obraId, weeklyData) {
// weeklyData as an array of objects: { dia_semana, hora_entrada, hora_salida, hora_colacion_inicio, hora_colacion_fin }
// Start a transaction in case of failure
const conn = await db.getConnection();
try {
await conn.beginTransaction();
for (const dia of weeklyData) {
// Upsert logic based on UNIQUE KEY uk_horario_obra_dia (obra_id, dia_semana)
await conn.execute(
`INSERT INTO configuracion_horarios
(obra_id, dia_semana, hora_entrada, hora_salida, hora_colacion_inicio, hora_colacion_fin)
VALUES (?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
hora_entrada = VALUES(hora_entrada),
hora_salida = VALUES(hora_salida),
hora_colacion_inicio = VALUES(hora_colacion_inicio),
hora_colacion_fin =FeriadosService class · javascript · L3-L70 (68 LOC)backend/src/services/feriados.service.js
class FeriadosService {
async getAll() {
const [rows] = await db.query('SELECT * FROM feriados WHERE activo = 1 ORDER BY fecha ASC');
return rows;
}
async getByDateRange(start, end) {
const [rows] = await db.query(
'SELECT * FROM feriados WHERE fecha BETWEEN ? AND ? AND activo = 1',
[start, end]
);
return rows;
}
async create(data) {
const { fecha, nombre, tipo, irrenunciable } = data;
const [result] = await db.query(
'INSERT INTO feriados (fecha, nombre, tipo, irrenunciable) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE nombre = VALUES(nombre), tipo = VALUES(tipo), irrenunciable = VALUES(irrenunciable), activo = 1',
[fecha, nombre, tipo || 'obra', irrenunciable || false]
);
return { id: result.insertId || null, ...data };
}
async update(id, data) {
const { fecha, nombre, tipo, irrenunciable } = data;
await db.query(
getAll method · javascript · L4-L7 (4 LOC)backend/src/services/feriados.service.js
async getAll() {
const [rows] = await db.query('SELECT * FROM feriados WHERE activo = 1 ORDER BY fecha ASC');
return rows;
}getByDateRange method · javascript · L9-L15 (7 LOC)backend/src/services/feriados.service.js
async getByDateRange(start, end) {
const [rows] = await db.query(
'SELECT * FROM feriados WHERE fecha BETWEEN ? AND ? AND activo = 1',
[start, end]
);
return rows;
}create method · javascript · L17-L24 (8 LOC)backend/src/services/feriados.service.js
async create(data) {
const { fecha, nombre, tipo, irrenunciable } = data;
const [result] = await db.query(
'INSERT INTO feriados (fecha, nombre, tipo, irrenunciable) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE nombre = VALUES(nombre), tipo = VALUES(tipo), irrenunciable = VALUES(irrenunciable), activo = 1',
[fecha, nombre, tipo || 'obra', irrenunciable || false]
);
return { id: result.insertId || null, ...data };
}update method · javascript · L26-L33 (8 LOC)backend/src/services/feriados.service.js
async update(id, data) {
const { fecha, nombre, tipo, irrenunciable } = data;
await db.query(
'UPDATE feriados SET fecha = ?, nombre = ?, tipo = ?, irrenunciable = ? WHERE id = ?',
[fecha, nombre, tipo || 'obra', irrenunciable || false, id]
);
return { id, ...data };
}syncNacionalHolidays method · javascript · L40-L69 (30 LOC)backend/src/services/feriados.service.js
async syncNacionalHolidays(year) {
try {
// Usamos la API de digital.gob.cl como primera opción
const response = await fetch(`https://apis.digital.gob.cl/fl/feriados/${year}`, {
headers: { 'User-Agent': 'BovedaLOLS/1.0' }
});
if (!response.ok) throw new Error('API Feriados no disponible');
const holidays = await response.json();
let count = 0;
for (const h of holidays) {
// Formato esperado: { nombre: "...", fecha: "YYYY-MM-DD", irrenunciable: "1/0" }
await this.create({
fecha: h.fecha,
nombre: h.nombre,
tipo: 'nacional',
irrenunciable: h.irrenunciable === "1" || h.irrenunciable === true
});
count++;
}
return { success: true, count };
} catch (error) {
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
FiscalizacionService class · javascript · L7-L130 (124 LOC)backend/src/services/fiscalizacion.service.js
class FiscalizacionService {
/**
* Búsqueda avanzada de trabajadores con múltiples filtros y cálculo de completitud en una sola query.
*/
async searchTrabajadores(filters) {
const {
q,
obra_id,
empresa_id,
cargo_id,
categoria_reporte,
activo,
completitud // '100', 'faltantes', 'todos'
} = filters;
// 1. Get total mandatory docs (required to calculate percentage in DB)
const [totalRows] = await db.query(
'SELECT COUNT(*) as total FROM tipos_documento WHERE obligatorio = TRUE AND activo = TRUE'
);
const totalObligatorios = totalRows[0].total;
let query = `
SELECT
t.*,
e.razon_social as empresa_nombre,
o.nombre as obra_nombre,
c.nombre as cargo_nombre,
COALESCE(docs.uploaded, 0) as docs_subidos,
? as docs_totalessearchTrabajadores method · javascript · L11-L125 (115 LOC)backend/src/services/fiscalizacion.service.js
async searchTrabajadores(filters) {
const {
q,
obra_id,
empresa_id,
cargo_id,
categoria_reporte,
activo,
completitud // '100', 'faltantes', 'todos'
} = filters;
// 1. Get total mandatory docs (required to calculate percentage in DB)
const [totalRows] = await db.query(
'SELECT COUNT(*) as total FROM tipos_documento WHERE obligatorio = TRUE AND activo = TRUE'
);
const totalObligatorios = totalRows[0].total;
let query = `
SELECT
t.*,
e.razon_social as empresa_nombre,
o.nombre as obra_nombre,
c.nombre as cargo_nombre,
COALESCE(docs.uploaded, 0) as docs_subidos,
? as docs_totales
FROM trabajadores t
LEFT JOIN empresas e ON t.empresa_id = e.id
LEFT JOIN obras o ON t.obra_id = o.id
LVersionService class · javascript · L7-L46 (40 LOC)backend/src/services/version.service.js
class VersionService {
constructor() {
this.versions = new Map();
this.initialized = false;
}
async init() {
if (this.initialized) return;
try {
const [rows] = await db.query('SELECT id, version FROM roles');
rows.forEach(r => {
this.versions.set(Number(r.id), Number(r.version));
});
this.initialized = true;
console.log(`[VersionService] Cargadas versiones para ${this.versions.size} roles.`);
} catch (err) {
console.error('[VersionService] Error inicializando versiones:', err);
}
}
get(rolId) {
return this.versions.get(Number(rolId)) || 1;
}
async increment(rolId) {
const id = Number(rolId);
const current = this.get(id);
const next = current + 1;
try {
await db.query('UPDATE roles SET version = ? WHERE id = ?', [next, id]);
this.versions.set(id, next);
constructor method · javascript · L8-L11 (4 LOC)backend/src/services/version.service.js
constructor() {
this.versions = new Map();
this.initialized = false;
}init method · javascript · L13-L25 (13 LOC)backend/src/services/version.service.js
async init() {
if (this.initialized) return;
try {
const [rows] = await db.query('SELECT id, version FROM roles');
rows.forEach(r => {
this.versions.set(Number(r.id), Number(r.version));
});
this.initialized = true;
console.log(`[VersionService] Cargadas versiones para ${this.versions.size} roles.`);
} catch (err) {
console.error('[VersionService] Error inicializando versiones:', err);
}
}get method · javascript · L27-L29 (3 LOC)backend/src/services/version.service.js
get(rolId) {
return this.versions.get(Number(rolId)) || 1;
}increment method · javascript · L31-L45 (15 LOC)backend/src/services/version.service.js
async increment(rolId) {
const id = Number(rolId);
const current = this.get(id);
const next = current + 1;
try {
await db.query('UPDATE roles SET version = ? WHERE id = ?', [next, id]);
this.versions.set(id, next);
console.log(`[VersionService] Rol #${id} incrementado a versión ${next}. Sesiones previas liquidadas.`);
return next;
} catch (err) {
console.error(`[VersionService] Error incrementando versión rol #${id}:`, err);
throw err;
}
}mountDebugRoutes function · javascript · L21-L125 (105 LOC)backend/src/utils/debug-toolkit.js
function mountDebugRoutes(app, auth) {
// Deep Health Check: verifies DB connectivity, key tables, JWT configuration
app.get('/api/health/deep', auth, requireSuperAdmin, async (req, res) => {
const checks = {};
// 1. Database connectivity
try {
const [rows] = await db.query('SELECT 1 as ok');
checks.database = { status: 'ok', detail: 'MySQL conectado' };
} catch (err) {
checks.database = { status: 'error', detail: err.message };
}
// 2. Permissions catalog check
try {
const [catalog] = await db.query('SELECT COUNT(*) as count FROM permisos_catalogo');
checks.permisos_catalogo = {
status: catalog[0].count > 0 ? 'ok' : 'warning',
count: catalog[0].count,
detail: catalog[0].count > 0 ? `${catalog[0].count} permisos registrados` : 'CATÁLOGO VACÍO - ejecutar migración'
};
} catch (err) {
About: code-quality intelligence by Repobility · https://repobility.com
extractRoutes function · javascript · L104-L118 (15 LOC)backend/src/utils/debug-toolkit.js
function extractRoutes(stack, basePath = '') {
stack.forEach(layer => {
if (layer.route) {
const methods = Object.keys(layer.route.methods).map(m => m.toUpperCase()).join(',');
routes.push({ method: methods, path: basePath + layer.route.path });
} else if (layer.name === 'router' && layer.handle.stack) {
const routerPath = layer.regexp.source
.replace('\\/?(?=\\/|$)', '')
.replace(/\\\//g, '/')
.replace(/\^/, '')
.replace(/\(\?:\(\[\^\\\/\]\+\?\)\)/, ':param');
extractRoutes(layer.handle.stack, basePath + routerPath);
}
});
}CrudTable function · typescript · L52-L412 (361 LOC)frontend/src/components/ui/CrudTable.tsx
export function CrudTable<T extends { id: number; activo?: boolean }>({
endpoint,
columns,
entityName,
entityNamePlural,
FormComponent,
searchPlaceholder,
canCreate = true,
canEdit = true,
canDelete = true,
canExport = true,
queryParams = {},
renderActions,
}: CrudTableProps<T>) {
const [rows, setRows] = useState<T[]>([]);
const [loading, setLoading] = useState(true);
const [search, setSearch] = useState('');
const [page, setPage] = useState(1);
const [pagination, setPagination] = useState({ total: 0, pages: 1 });
const [modalOpen, setModalOpen] = useState(false);
const [editItem, setEditItem] = useState<T | null>(null);
const fetchData = useCallback(async () => {
setLoading(true);
try {
const params = new URLSearchParams({
q: search,
page: page.toString(),
limit: '15'
});
Object.entries(queryParams).forEacuseStandardHeader function · typescript · L12-L28 (17 LOC)frontend/src/components/ui/PageHeader.tsx
export function useStandardHeader({ title, icon: Icon, badgeCount, actions }: PageHeaderProps) {
const headerTitle = React.useMemo(() => (
<div className="flex items-center gap-2 md:gap-3">
<Icon className="h-5 w-5 md:h-6 md:w-6 text-brand-primary shrink-0" />
<div className="flex flex-col md:flex-row md:items-center gap-0.5 md:gap-2 min-w-0">
<h1 className="text-sm md:text-lg font-bold text-brand-dark truncate">{title}</h1>
{badgeCount !== undefined && badgeCount > 0 && (
<span className="bg-[#E8E8ED] text-muted-foreground text-xs font-semibold px-2 py-0.5 rounded-full w-fit">
{badgeCount}
</span>
)}
</div>
</div>
), [title, Icon, badgeCount]);
useSetPageHeader(headerTitle, actions);
}page 1 / 2next ›