feat: subdomain routing por tenant — refac-xxx.nexusautoparts.com

- Nginx wildcard config: *.nexusautoparts.com routes to POS app with X-Tenant-Subdomain header
- middleware_tenant.py: resolves subdomain -> tenant_id via nexus_master.tenants.subdomain
- auth_bp: login and employee list endpoints accept tenant from subdomain, URL param, or body
- login.html: auto-detects tenant from subdomain, shows business name, falls back to ?tenant=ID
- tenant_manager: generates subdomain slug from business name on provision_tenant()
- Migration v1.2: adds subdomain column + unique index to tenants table
- setup-nginx.sh: one-command install script for the nginx config

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-02 07:16:49 +00:00
parent bdbbc78a15
commit 6628f2deef
8 changed files with 360 additions and 20 deletions

View File

@@ -6,6 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Nexus Autoparts — Iniciar Sesión</title>
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
<meta name="theme-color" content="#F5A623" />
<style>
/* =====================================================================
@@ -1124,10 +1126,23 @@
/* ------------------------------------------------------------------
TRIGGER LOGIN (demo)
------------------------------------------------------------------ */
// Get tenant_id from URL or localStorage
var tenantId = new URLSearchParams(window.location.search).get('tenant')
// Tenant resolution: subdomain (server-side) > URL param > localStorage
var _serverTenantId = {{ tenant_id | default('null') | tojson }};
var _serverTenantName = {{ tenant_name | default('null') | tojson }};
var _serverSubdomain = {{ tenant_subdomain | default('null') | tojson }};
var tenantId = _serverTenantId
|| new URLSearchParams(window.location.search).get('tenant')
|| localStorage.getItem('pos_tenant_id')
|| '11'; // Default tenant — remove in production when multi-tenant selector exists
// Show business name from subdomain if available
if (_serverTenantName) {
var brandNameEl = document.querySelector('.brand-name');
var brandSubEl = document.querySelector('.brand-sub');
if (brandNameEl) brandNameEl.textContent = _serverTenantName;
if (brandSubEl) brandSubEl.textContent = 'Punto de Venta';
}
// Device ID (persistent)
var deviceId = localStorage.getItem('pos_device_id');
if (!deviceId) {
@@ -1277,10 +1292,14 @@
------------------------------------------------------------------ */
function loadEmployees() {
if (!tenantId) {
document.getElementById('usersGrid').innerHTML = '<div style="text-align:center;padding:var(--space-4);color:var(--color-error);">No se especificó tenant. Agrega ?tenant=ID a la URL.</div>';
document.getElementById('usersGrid').innerHTML = '<div style="text-align:center;padding:var(--space-4);color:var(--color-error);">No se especificó tenant. Agrega ?tenant=ID a la URL o usa un subdominio.</div>';
return;
}
fetch('/pos/api/auth/employees/' + tenantId)
// If subdomain is set, the server already knows the tenant — use /employees endpoint
var empUrl = _serverSubdomain
? '/pos/api/auth/employees'
: '/pos/api/auth/employees/' + tenantId;
fetch(empUrl)
.then(function(r) { return r.json(); })
.then(function(data) {
var grid = document.getElementById('usersGrid');
@@ -1342,5 +1361,8 @@
</script>
<script src="/pos/static/js/sync-engine.js"></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
</body>
</html>