feat(multi-tenant): configurar las 3 empresas oficiales (Telcotronics, XSYSTEM, DOMUS) y selector rapido de entorno

This commit is contained in:
2026-10-23 09:59:12 -05:00
parent 78f64291f5
commit 48323ea353
7 changed files with 137 additions and 39 deletions
+36 -20
View File
@@ -27,23 +27,37 @@ async function setup() {
)
`);
// Ensure demo company exists FIRST (before migrations reference it)
const [existing] = await conn.query('SELECT id FROM companies WHERE slug = ?', ['demo']);
let companyId;
if (existing.length === 0) {
// 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 (name, slug, plan, max_users, max_tickets) VALUES (?, ?, ?, ?, ?)',
['Demo Company', 'demo', 'enterprise', 100, 10000]
'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]
);
companyId = 1;
console.log('+ Empresa demo creada');
console.log(`+ Empresa ${ec.name} creada (id=${ec.id})`);
} else {
companyId = existing[0].id;
console.log('~ Empresa demo ya existe (id=' + companyId + ')');
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})`);
}
}
// Ensure company_id default value references existing company
const defCompanyId = companyId;
const companyId = 1;
const defCompanyId = 1;
// Migrate existing tables
const migrateColumn = async (table, column, definition) => {
@@ -129,21 +143,23 @@ async function setup() {
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
`);
// Seed departments
const [depts] = await conn.query('SELECT COUNT(*) as c FROM departments WHERE company_id = ?', [companyId]);
// 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 ?',
[[
[companyId, 'Soporte Técnico', 'Problemas técnicos y de software'],
[companyId, 'Facturación', 'Problemas de facturación y pagos'],
[companyId, 'Ventas', 'Consultas sobre ventas y productos'],
[companyId, 'General', 'Consultas generales']
[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');
console.log(`+ Departamentos seed insertados para ${ec.name}`);
} else {
console.log('~ Departamentos ya existen');
console.log(`~ Departamentos ya existen para ${ec.name}`);
}
}
// Seed users
+3
View File
@@ -25,6 +25,9 @@ module.exports = {
if (req.session.user && req.session.user.company_id) {
const company = await Company.findById(req.session.user.company_id);
res.locals.company = company;
if (req.session.user.rol === 'superadmin') {
res.locals.allCompanies = await Company.findAll();
}
}
next();
},
+1 -1
View File
@@ -2,7 +2,7 @@ const db = require('../config/db');
const Company = {
findAll: async () => {
const [rows] = await db.query('SELECT * FROM companies ORDER BY created_at DESC');
const [rows] = await db.query('SELECT * FROM companies ORDER BY id ASC');
return rows;
},
findById: async (id) => {
+14
View File
@@ -60,6 +60,20 @@ body {
color: #e2e8f0;
}
.company-nav-pill.dropdown-toggle::after {
display: none !important;
}
.company-nav-pill.dropdown-toggle {
cursor: pointer;
transition: all 0.2s ease;
}
.company-nav-pill.dropdown-toggle:hover {
background: rgba(255, 255, 255, 0.14) !important;
border-color: rgba(255, 255, 255, 0.28) !important;
}
.company-plan-badge {
background: linear-gradient(135deg, #0284c7 0%, #38bdf8 100%);
color: #ffffff;
+28
View File
@@ -69,4 +69,32 @@ router.post('/companies/update/:id', isSuperAdmin, async (req, res) => {
}
});
router.get('/switch/:id', isSuperAdmin, async (req, res) => {
try {
const targetId = parseInt(req.params.id);
const company = await Company.findById(targetId);
if (!company) {
req.flash('error_msg', 'Empresa no encontrada');
return res.redirect('/saas/companies');
}
req.session.user.company_id = company.id;
if (req.session.user.id) {
await User.update(req.session.user.id, { company_id: company.id });
}
req.flash('success_msg', `Cambiaste de entorno a: ${company.name}`);
const referrer = req.get('Referrer');
// Redirigir de forma segura al referrer o al dashboard
if (referrer && !referrer.includes('/login') && !referrer.includes('/logout')) {
return res.redirect(referrer);
}
res.redirect('/admin/dashboard');
} catch (error) {
console.error('Error switching company:', error);
req.flash('error_msg', 'Error al cambiar de empresa');
res.redirect('/saas/companies');
}
});
module.exports = router;
+32
View File
@@ -15,12 +15,44 @@
</div>
</a>
<% if (typeof company !== 'undefined' && company) { %>
<% if (typeof user !== 'undefined' && user && user.rol === 'superadmin' && typeof allCompanies !== 'undefined' && allCompanies.length > 1) { %>
<div class="dropdown ms-2 d-none d-lg-inline-block">
<button class="company-nav-pill dropdown-toggle d-flex align-items-center gap-1.5 border-0 bg-transparent text-white" type="button" data-bs-toggle="dropdown" aria-expanded="false" title="Cambiar Empresa (Superadmin)">
<i class="fas fa-building text-info" style="font-size: 0.75rem;"></i>
<span class="company-name-text fw-semibold"><%= company.name %></span>
<span class="badge company-plan-badge"><%= company.plan %></span>
<i class="fas fa-chevron-down ms-1 text-muted" style="font-size: 0.62rem;"></i>
</button>
<ul class="dropdown-menu dropdown-menu-dark shadow-lg border-secondary mt-1 py-1" style="font-size: 0.85rem; min-width: 220px;">
<li class="dropdown-header text-uppercase text-muted fw-bold" style="font-size: 0.65rem; letter-spacing: 0.5px;">
<i class="fas fa-exchange-alt me-1 text-info"></i> Entornos / Empresas
</li>
<% allCompanies.forEach(c => { %>
<li>
<a class="dropdown-item d-flex align-items-center justify-content-between py-2 <%= c.id === company.id ? 'active' : '' %>" href="/saas/switch/<%= c.id %>">
<span class="fw-medium"><%= c.name %></span>
<span class="badge <%= c.id === company.id ? 'bg-info text-dark' : 'bg-dark text-muted border border-secondary' %>" style="font-size: 0.65rem;">
<%= c.slug %>
</span>
</a>
</li>
<% }) %>
<li><hr class="dropdown-divider my-1 border-secondary"></li>
<li>
<a class="dropdown-item py-1.5 text-warning small d-flex align-items-center gap-1.5" href="/saas/companies">
<i class="fas fa-sliders-h"></i> Gestionar Empresas
</a>
</li>
</ul>
</div>
<% } else { %>
<div class="company-nav-pill ms-2 d-none d-lg-flex align-items-center gap-1.5">
<i class="fas fa-building text-info" style="font-size: 0.75rem;"></i>
<span class="company-name-text"><%= company.name %></span>
<span class="badge company-plan-badge"><%= company.plan %></span>
</div>
<% } %>
<% } %>
</div>
<% if (user) { %>
+5
View File
@@ -102,9 +102,14 @@
<% } %>
</td>
<td class="text-end">
<div class="d-inline-flex align-items-center gap-1">
<a href="/saas/switch/<%= c.id %>" class="btn btn-sm <%= typeof company !== 'undefined' && company && company.id === c.id ? 'btn-info text-dark fw-bold disabled' : 'btn-outline-primary' %>" title="<%= typeof company !== 'undefined' && company && company.id === c.id ? 'Empresa activa actualmente' : 'Cambiar y administrar esta empresa' %>">
<i class="fas <%= typeof company !== 'undefined' && company && company.id === c.id ? 'fa-check' : 'fa-exchange-alt' %>"></i>
</a>
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#editModal<%= c.id %>" title="Editar cuotas y plan">
<i class="fas fa-edit"></i>
</button>
</div>
</td>
</tr>
<% }) %>