# Sprint 1.2 · Schema de innovium_master

> **Para vos, Ricci.** Explicación en castellano de las tablas de la BD maestra.

## ¿Qué es `innovium_master`?

Es la **BD que registra qué tenants existen**. Cada vez que llega un request a Innovium, el `TenantResolver` consulta acá para saber:

1. ¿Existe el slug del subdominio? (ej: `demo`, `infinia`)
2. ¿Está activo el tenant? (no suspendido, no eliminado)
3. ¿A qué BD apunta? (ej: `innovium_demo`, `innovium_infinia`)
4. ¿Qué configuración tiene? (plan, branding, features habilitadas)

**Sin `innovium_master`, Innovium no sabe a qué funeraria pertenece cada request.**

---

## Las 5 tablas de master

### 1. `tenants` — el catálogo de funerarias

| Columna | Tipo | Para qué |
|---|---|---|
| `id` | BIGINT auto | id |
| `slug` | VARCHAR(50) UNIQUE | identificador URL: `demo`, `infinia`. Solo letras minúsculas y guiones. |
| `nombre` | VARCHAR(150) | nombre legal: "Funeraria Infinia SpA" |
| `nombre_corto` | VARCHAR(80) | nombre comercial: "Infinia" |
| `db_name` | VARCHAR(80) UNIQUE | BD asociada: `innovium_infinia` |
| `storage_path` | VARCHAR(120) UNIQUE | path en R2: `tenants/infinia` |
| `dominio_personalizado` | VARCHAR(200) NULL | si en el futuro quieren usar `sistema.funerariainfinia.cl` |
| `estado` | ENUM | `activo`, `suspendido`, `eliminado`, `en_trial` |
| `plan_tipo` | VARCHAR(50) NULL | placeholder para Sprint 2.x billing |
| `branding_color_primario` | VARCHAR(7) NULL | hex color custom (ej: `#7C3AED`). NULL = usa default Innovium. |
| `branding_logo_url` | VARCHAR(500) NULL | URL en R2 al logo de la funeraria |
| `creado_en` | DATETIME | UTC |
| `actualizado_en` | DATETIME | UTC, auto-update |
| `eliminado_en` | DATETIME NULL | soft delete |

**Decisión clave:** `slug` validado en código con regex `^[a-z][a-z0-9-]{2,30}$`. Sin esto, alguien podría poner slug `'; DROP TABLE--` y hacer SQL injection.

**Decisión clave:** cada tenant tiene su `db_name` y `storage_path` propios. **Aislamiento físico**, no compartimos tablas.

---

### 2. `tenant_features` — qué módulos tiene habilitado cada tenant

| Columna | Tipo | Para qué |
|---|---|---|
| `id` | BIGINT auto | |
| `tenant_id` | BIGINT FK | |
| `feature_slug` | VARCHAR(50) | `acompanamiento_duelo`, `obituarios_publicos`, etc. |
| `habilitada` | TINYINT(1) | 1=on, 0=off |
| `configuracion` | JSON NULL | settings específicas (ej: el psicólogo de duelo) |
| `creado_en` | DATETIME | |
| `actualizado_en` | DATETIME | |

**UNIQUE en `(tenant_id, feature_slug)`**: un tenant no puede tener la misma feature dos veces.

**Para Sprint 1.2 sembramos:**
- `multi_tenant` (true para todos)
- `firma_digital` (true)
- `offline_mode` (true)
- `acompanamiento_duelo` (false por default, true para Infinia como diferenciador)
- `obituarios_publicos` (false por default)

---

### 3. `tenant_billing` — datos comerciales del tenant

> ⚠️ **En Sprint 1.2 esta tabla se crea vacía** — no hacemos billing real todavía. Pero la estructura queda lista para Sprint 2.x cuando integremos pagos.

| Columna | Tipo | Para qué |
|---|---|---|
| `id` | BIGINT auto | |
| `tenant_id` | BIGINT FK | |
| `plan_codigo` | VARCHAR(50) NULL | `essential`, `pro`, `enterprise` |
| `monto_mensual_clp` | INT NULL | precio en CLP |
| `ciclo` | ENUM NULL | `mensual`, `anual` |
| `proximo_cobro` | DATE NULL | |
| `metodo_pago` | VARCHAR(50) NULL | `transferencia`, `webpay`, etc. |
| `estado_billing` | ENUM | `al_dia`, `vencido`, `suspendido` |
| `creado_en` | DATETIME | |
| `actualizado_en` | DATETIME | |

---

### 4. `tenant_admin_users` — admins iniciales antes de tener usuarios internos

> Esta tabla resuelve un problema "huevo y gallina": para crear usuarios en `innovium_<tenant>` necesitamos un usuario admin que ya esté logueado, pero un tenant nuevo no tiene usuarios. Solución: cuando se crea un tenant, se registra acá el admin inicial.

| Columna | Tipo | Para qué |
|---|---|---|
| `id` | BIGINT auto | |
| `tenant_id` | BIGINT FK | |
| `email` | VARCHAR(180) | email del admin inicial |
| `nombre` | VARCHAR(150) | |
| `password_hash` | VARCHAR(255) | Argon2id |
| `email_verificado_en` | DATETIME NULL | cuando confirmó por mail |
| `migrado_a_tenant` | TINYINT(1) | 1 si ya se migró a la tabla `users` del tenant |
| `creado_en` | DATETIME | |
| `actualizado_en` | DATETIME | |

**Flujo:**
1. CronoSystems crea tenant `infinia` → comando CLI inserta admin en `tenant_admin_users`
2. Admin recibe email, pone password, hace primer login
3. Sistema migra ese admin a `innovium_infinia.users` con rol `tenant_admin`
4. Marca `migrado_a_tenant=1` en `tenant_admin_users`
5. Desde acá en adelante, los logins van directo contra `innovium_infinia.users`

**Para Sprint 1.2 simplificamos:** sembramos directamente el admin en `users` del tenant via seed, sin pasar por este flujo. La tabla queda lista para implementación real en Sprint 2.x.

---

### 5. `tenant_audit_log` — log cross-tenant de operaciones de Crono Systems

> Distinto del `audit_log` que cada tenant tiene en su propia BD. Este registra cosas que solo Crono Systems hace: crear tenant, suspender, cambiar plan, etc.

| Columna | Tipo | Para qué |
|---|---|---|
| `id` | BIGINT auto | |
| `tenant_id` | BIGINT FK NULL | NULL si es acción global |
| `accion` | VARCHAR(100) | `tenant.creado`, `tenant.suspendido`, etc. |
| `detalle` | JSON NULL | metadata |
| `ip` | VARCHAR(45) | desde dónde se hizo |
| `creado_en` | DATETIME | |

---

## Tabla bonus que ya creamos: `migrations`

Existe automáticamente en cada BD (master y tenant). El `Migrator` la usa para tracking. **No se modela acá**, ya está implementada.

---

## Lo que NO está en este sprint

- 🚫 Selector visual de tenants en UI (Sprint 2.x)
- 🚫 Billing real con WebPay/Mercado Pago (Sprint 2.x)
- 🚫 Email confirmation en flujo de admin inicial (Sprint 2.x)
- 🚫 Migración de admin de `tenant_admin_users` a `users` real (Sprint 2.x — por ahora seed directo)
- 🚫 Branding custom funcionando en UI (Sprint 1.4 cuando ya haya admin de tenant)

---

## Indices importantes

```sql
UNIQUE KEY uq_tenants_slug (slug)
UNIQUE KEY uq_tenants_db_name (db_name)
UNIQUE KEY uq_tenants_storage_path (storage_path)
KEY idx_tenants_estado (estado)
KEY idx_tenants_eliminado_en (eliminado_en)

UNIQUE KEY uq_tenant_features (tenant_id, feature_slug)
KEY idx_tenant_features_habilitada (habilitada)

UNIQUE KEY uq_tenant_admin_email (tenant_id, email)
KEY idx_tenant_audit_log_tenant_creado (tenant_id, creado_en)
```

---

## Datos seed para Sprint 1.2

### Tenant `demo`
- nombre: "Funeraria Demo"
- nombre_corto: "Demo"
- db_name: `innovium_demo`
- estado: `activo`
- features: multi_tenant, firma_digital, offline_mode (all true)
- usuarios: los 6 que ya creamos en Sprint 1.1

### Tenant `infinia`
- nombre: "Funeraria Infinia SpA"
- nombre_corto: "Infinia"
- db_name: `innovium_infinia`
- estado: `activo`
- features: multi_tenant, firma_digital, offline_mode, acompanamiento_duelo (todos true)
- usuarios: 6 usuarios sintéticos chilenos similares a demo
  - `superadmin@infinia.cl` (Super Admin)
  - `admin@infinia.cl` (Tenant Admin)
  - `gerente@infinia.cl` (Gerente)
  - `vendedor@infinia.cl` (Vendedor)
  - `operativo@infinia.cl` (Operativo)
  - `contador@infinia.cl` (Contador)
  - Password de todos: `Innovium2026!`
