const mysql = require('mysql2/promise'); require('dotenv').config(); async function setup() { const conn = await mysql.createConnection({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, multipleStatements: true }); await conn.query(`CREATE DATABASE IF NOT EXISTS \`${process.env.DB_NAME}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`); await conn.query(`USE \`${process.env.DB_NAME}\``); // Create companies table await conn.query(` CREATE TABLE IF NOT EXISTS companies ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(200) NOT NULL, slug VARCHAR(100) NOT NULL UNIQUE, plan ENUM('free', 'pro', 'enterprise') DEFAULT 'free', max_users INT DEFAULT 5, max_tickets INT DEFAULT 100, is_active TINYINT(1) DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) `); // Ensure official ecosystem companies exist: Telcotronics, XSYSTEM, DOMUS // Migrate legacy demo / testcorp / nuevaemp if present await conn.query(`UPDATE companies SET name = 'Telcotronics', slug = 'telcotronics', plan = 'enterprise', max_users = 100, max_tickets = 10000 WHERE slug = 'demo' OR id = 1`); await conn.query(`UPDATE companies SET name = 'XSYSTEM', slug = 'xsystem', plan = 'enterprise', max_users = 100, max_tickets = 10000 WHERE slug = 'testcorp' OR id = 2`); await conn.query(`UPDATE companies SET name = 'DOMUS', slug = 'domus', plan = 'enterprise', max_users = 100, max_tickets = 10000 WHERE slug = 'nuevaemp' OR id = 3`); const ecosystemCompanies = [ { id: 1, name: 'Telcotronics', slug: 'telcotronics', plan: 'enterprise', max_users: 100, max_tickets: 10000 }, { id: 2, name: 'XSYSTEM', slug: 'xsystem', plan: 'enterprise', max_users: 100, max_tickets: 10000 }, { id: 3, name: 'DOMUS', slug: 'domus', plan: 'enterprise', max_users: 100, max_tickets: 10000 } ]; for (const ec of ecosystemCompanies) { const [found] = await conn.query('SELECT id FROM companies WHERE slug = ? OR id = ?', [ec.slug, ec.id]); if (found.length === 0) { await conn.query( 'INSERT INTO companies (id, name, slug, plan, max_users, max_tickets) VALUES (?, ?, ?, ?, ?, ?)', [ec.id, ec.name, ec.slug, ec.plan, ec.max_users, ec.max_tickets] ); console.log(`+ Empresa ${ec.name} creada (id=${ec.id})`); } else { await conn.query( 'UPDATE companies SET name = ?, slug = ?, plan = ?, max_users = ?, max_tickets = ? WHERE id = ?', [ec.name, ec.slug, ec.plan, ec.max_users, ec.max_tickets, found[0].id] ); console.log(`~ Empresa ${ec.name} sincronizada (id=${found[0].id})`); } } const companyId = 1; const defCompanyId = 1; // Migrate existing tables const migrateColumn = async (table, column, definition) => { try { await conn.query(`ALTER TABLE \`${table}\` ADD COLUMN ${definition}`); console.log(` + columna ${column} agregada a ${table}`); } catch (e) { if (e.errno === 1060) console.log(` ~ ${table}.${column} ya existe`); else throw e; } }; console.log('Migrando tablas...'); await migrateColumn('users', 'company_id', `company_id INT NOT NULL DEFAULT ${defCompanyId} AFTER id`); await migrateColumn('users', 'is_active', 'is_active TINYINT(1) DEFAULT 1'); await migrateColumn('users', 'invite_token', 'invite_token VARCHAR(100) NULL'); await migrateColumn('users', 'invite_token_expires', 'invite_token_expires TIMESTAMP NULL'); await migrateColumn('users', 'invited_by', 'invited_by INT NULL'); await migrateColumn('users', 'provider', "provider ENUM('local', 'webcontrol', 'google', 'facebook') DEFAULT 'local'"); await migrateColumn('users', 'provider_id', "provider_id VARCHAR(150) NULL"); await migrateColumn('users', 'avatar_url', "avatar_url VARCHAR(255) NULL"); try { await conn.query("ALTER TABLE users MODIFY COLUMN password VARCHAR(255) NULL"); console.log(' + users.password modificado a nullable'); } catch (e) { // Ignorar si ya es nullable } // Alter users.rol to include superadmin try { await conn.query("ALTER TABLE users MODIFY COLUMN rol ENUM('superadmin','admin','agent','client') DEFAULT 'client'"); console.log(' + users.rol actualizado a superadmin'); } catch (e) { if (e.errno === 1060 || e.errno === 1062 || e.errno === 1265) console.log(' ~ users.rol ya actualizado'); else throw e; } // Add FKs for users for (const fk of [ 'ALTER TABLE users ADD FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE', 'ALTER TABLE users ADD FOREIGN KEY (invited_by) REFERENCES users(id) ON DELETE SET NULL' ]) { try { await conn.query(fk); } catch (e) { if (e.errno !== 1826 && e.errno !== 1005) { throw e; } } } await migrateColumn('departments', 'company_id', `company_id INT NOT NULL DEFAULT ${defCompanyId} AFTER id`); try { await conn.query('ALTER TABLE departments ADD FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE'); } catch (e) { if (e.errno !== 1826 && e.errno !== 1005) throw e; } await migrateColumn('tickets', 'company_id', `company_id INT NOT NULL DEFAULT ${defCompanyId} AFTER id`); try { await conn.query('ALTER TABLE tickets ADD FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE'); } catch (e) { if (e.errno !== 1826 && e.errno !== 1005) throw e; } // Create ticket_visitas table await conn.query(` CREATE TABLE IF NOT EXISTS ticket_visitas ( id INT AUTO_INCREMENT PRIMARY KEY, company_id INT NOT NULL, ticket_id INT NOT NULL, external_id VARCHAR(100) NULL, tecnico_email VARCHAR(150) NOT NULL, tecnico_nombre VARCHAR(150) NULL, titulo VARCHAR(255) NOT NULL, descripcion TEXT NULL, ubicacion VARCHAR(255) NULL, latitud DECIMAL(10, 7) NULL, longitud DECIMAL(10, 7) NULL, fecha_visita DATETIME NULL, fecha_fin DATETIME NULL, estado ENUM('programada', 'en_camino', 'en_sitio', 'completada', 'cancelada') DEFAULT 'programada', checklist_json TEXT NULL, historial_json LONGTEXT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX (ticket_id), INDEX (company_id), INDEX (external_id), FOREIGN KEY (ticket_id) REFERENCES tickets(id) ON DELETE CASCADE, FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; `); // Seed departments for all ecosystem companies for (const ec of ecosystemCompanies) { const [depts] = await conn.query('SELECT COUNT(*) as c FROM departments WHERE company_id = ?', [ec.id]); if (depts[0].c === 0) { await conn.query( 'INSERT INTO departments (company_id, nombre, descripcion) VALUES ?', [[ [ec.id, 'Soporte Técnico', 'Problemas técnicos y de software'], [ec.id, 'Facturación', 'Problemas de facturación y pagos'], [ec.id, 'Ventas', 'Consultas sobre ventas y productos'], [ec.id, 'General', 'Consultas generales'] ]] ); console.log(`+ Departamentos seed insertados para ${ec.name}`); } else { console.log(`~ Departamentos ya existen para ${ec.name}`); } } // Seed users const bcrypt = require('bcryptjs'); const hash = await bcrypt.hash('123456', 10); const users = [ ['super@helpdesk.com', 'Super Admin', 'superadmin'], ['admin@helpdesk.com', 'Admin', 'admin'], ['agent@helpdesk.com', 'Agente', 'agent'], ['client@helpdesk.com', 'Cliente', 'client'] ]; for (const [email, nombre, rol] of users) { const [u] = await conn.query('SELECT id FROM users WHERE email = ?', [email]); if (u.length === 0) { await conn.query( 'INSERT INTO users (company_id, nombre, email, password, rol) VALUES (?, ?, ?, ?, ?)', [companyId, nombre, email, hash, rol] ); console.log(`+ Usuario ${email} creado`); } else { await conn.query('UPDATE users SET company_id = ?, rol = ? WHERE email = ? AND rol != ?', [companyId, rol, email, 'superadmin']); console.log(`~ Usuario ${email} actualizado`); } } // Ensure company_id is set for all orphan records await conn.query('UPDATE users SET company_id = ? WHERE company_id IS NULL OR company_id = 0', [companyId]); await conn.query('UPDATE departments SET company_id = ? WHERE company_id IS NULL OR company_id = 0', [companyId]); await conn.query('UPDATE tickets SET company_id = ? WHERE company_id IS NULL OR company_id = 0', [companyId]); console.log('\nBase de datos configurada correctamente'); await conn.end(); process.exit(0); } setup().catch(err => { console.error('Error:', err.message); process.exit(1); });