← back to Mauricio-Alvarez-T__Boveda-LOLS

Function bodies 67 total

All specs Real LLM only Function bodies
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(d
log 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 o
listMigrationFiles 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}`);
        thro
runMaintenanceTasks 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 = 'trabaja
run 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 first
validateEnv 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_col
All 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_totales
searchTrabajadores 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
            L
VersionService 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).forEac
useStandardHeader 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 ›