diff --git a/config.py b/config.py
index d7573bf..4fcf7d8 100644
--- a/config.py
+++ b/config.py
@@ -11,6 +11,9 @@ if not DB_URL:
"Example: postgresql://user:pass@localhost/nexus_autoparts"
)
+MASTER_DB_URL = os.environ.get("MASTER_DB_URL") or DB_URL
+TENANT_DB_URL_TEMPLATE = os.environ.get("TENANT_DB_URL_TEMPLATE") or DB_URL.replace("nexus_autoparts", "{db_name}")
+
# Legacy SQLite path (used only by migration script)
SQLITE_PATH = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
diff --git a/dashboard/admin.html b/dashboard/admin.html
index eeab4e1..8c6b907 100644
--- a/dashboard/admin.html
+++ b/dashboard/admin.html
@@ -92,6 +92,14 @@
+
+
@@ -660,6 +668,35 @@
+
+
+
+
+
+
+
+
+
+ | ID |
+ Nombre |
+ WhatsApp |
+ Marketplace |
+ MercadoLibre |
+ Acciones |
+
+
+
+ |
+
+
+
+
+
+
diff --git a/dashboard/admin.js b/dashboard/admin.js
index 62fe78d..fcd51f5 100644
--- a/dashboard/admin.js
+++ b/dashboard/admin.js
@@ -121,6 +121,9 @@ function showSection(sectionId) {
case 'users':
loadUsers();
break;
+ case 'tenants':
+ loadTenants();
+ break;
}
}
@@ -2074,3 +2077,99 @@ async function toggleUserActive(userId, currentActive) {
showAlert(e.message, 'error');
}
}
+
+// ─── Tenants / Modules ─────────────────────────────────────────────────────
+
+async function loadTenants() {
+ var token = localStorage.getItem('access_token');
+ var tbody = document.getElementById('tenantsTable');
+ tbody.innerHTML = ' |
';
+
+ try {
+ var res = await fetch('/api/admin/tenants', {
+ headers: { 'Authorization': 'Bearer ' + token }
+ });
+ if (!res.ok) throw new Error('Error al cargar tenants (' + res.status + ')');
+ var data = await res.json();
+ var tenants = data.tenants || [];
+
+ if (tenants.length === 0) {
+ tbody.innerHTML = '| No hay tenants activos |
';
+ return;
+ }
+
+ // Load modules for each tenant
+ var modulesMap = {};
+ await Promise.all(tenants.map(async function(t) {
+ try {
+ var mres = await fetch('/api/admin/tenants/' + t.id + '/modules', {
+ headers: { 'Authorization': 'Bearer ' + token }
+ });
+ if (mres.ok) {
+ modulesMap[t.id] = await mres.json();
+ } else {
+ modulesMap[t.id] = {};
+ }
+ } catch (e) {
+ modulesMap[t.id] = {};
+ }
+ }));
+
+ renderTenantsTable(tenants, modulesMap);
+ } catch (e) {
+ tbody.innerHTML = '| ' + e.message + ' |
';
+ }
+}
+
+function renderTenantsTable(tenants, modulesMap) {
+ var tbody = document.getElementById('tenantsTable');
+ tbody.innerHTML = tenants.map(function(t) {
+ var mods = modulesMap[t.id] || {};
+ function toggleBtn(tenantId, key, enabled) {
+ var label = enabled ? 'Activado' : 'Desactivado';
+ var cls = enabled ? 'btn-primary' : 'btn-secondary';
+ return '';
+ }
+ return '' +
+ '| ' + t.id + ' | ' +
+ '' + (t.name || '-') + ' | ' +
+ '' + toggleBtn(t.id, 'whatsapp_enabled', !!mods.whatsapp_enabled) + ' | ' +
+ '' + toggleBtn(t.id, 'marketplace_enabled', !!mods.marketplace_enabled) + ' | ' +
+ '' + toggleBtn(t.id, 'meli_enabled', !!mods.meli_enabled) + ' | ' +
+ ' | ' +
+ '
';
+ }).join('');
+}
+
+async function toggleTenantModule(tenantId, key, currentValue) {
+ var token = localStorage.getItem('access_token');
+ var moduleNames = {
+ 'whatsapp_enabled': 'WhatsApp',
+ 'marketplace_enabled': 'Marketplace',
+ 'meli_enabled': 'MercadoLibre'
+ };
+ var action = currentValue ? 'desactivar' : 'activar';
+ if (!confirm('¿Seguro que deseas ' + action + ' ' + moduleNames[key] + ' para el tenant #' + tenantId + '?')) return;
+
+ try {
+ var payload = {};
+ payload[key] = !currentValue;
+ var res = await fetch('/api/admin/tenants/' + tenantId + '/modules', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': 'Bearer ' + token
+ },
+ body: JSON.stringify(payload)
+ });
+ if (!res.ok) {
+ var err = await res.json();
+ throw new Error(err.error || 'Error al actualizar módulo');
+ }
+ showAlert(moduleNames[key] + ' ' + (currentValue ? 'desactivado' : 'activado') + ' para tenant #' + tenantId);
+ loadTenants();
+ } catch (e) {
+ showAlert(e.message, 'error');
+ }
+}
diff --git a/dashboard/landing.html b/dashboard/landing.html
index 27c16f3..049aa8e 100644
--- a/dashboard/landing.html
+++ b/dashboard/landing.html
@@ -4,7 +4,7 @@
Nexus Autoparts — Sistema completo para refaccionarias
-
+
@@ -8,10 +8,12 @@
+
+
@@ -188,10 +190,10 @@