# Plan de implementación — MT-1 Backoffice vendedor

> **Estado:** **implementado 2026-07-28**.  
> **Depende de:** MT-0 (hecho, commit `a2572cc`).  
> **Relacionado:** `PLAN_MULTITENANT.md` §5 y §9 · `PLAN_ONBOARDING.md` (password forzado / checklist = no en MT-1).

---

## 1. Objetivo

Que el **superadmin** pueda, desde `/super`:

1. Ver el estado de la plataforma (KPI de tenants).
2. **Dar de alta** un laboratorio completo y usable.
3. Ver **detalle**, editar **metadatos de plataforma**, **suspender/reactivar**.
4. **Resetear** la contraseña del admin del tenant (temporal + flag).

**Criterio de salida:** creas un lab nuevo → el dueño hace login → solo ve datos de su tenant; si suspendes, no entra.

---

## 2. Decisiones cerradas (2026-07-28)

| Tema | Decisión |
|------|----------|
| Alcance | **MVP del plan**: dashboard, listado, alta+provisión, detalle, editar metadatos, suspender/reactivar, reset admin. Sin impersonación ni métricas de ventas. |
| Provisión al alta | **Paquete completo**: tenant + sucursal principal + consecutivo + maximos + admin dueño + **catálogo base precios $0** (checkbox ON por defecto). |
| Credenciales admin | **Username + contraseña temporal** que el superadmin escribe; se muestran **una sola vez** tras crear. |
| UI | **Páginas completas** + DataTable en listado (no modales AJAX). |
| Edición post-alta | **Solo metadatos de plataforma** (nombre, contacto, plan, notas, activo). No catálogo ni sucursales del lab. |
| Slug | **Auto desde nombre**, editable en el alta; **único**; tras crear **inmutable** (solo lectura en detalle). |
| Fuera de MT-1 | Onboarding checklist dueño, force-password UI (columnas ya existen; wiring = con onboarding), impersonación, cobros, subdominios. |

---

## 3. Rutas y menú

Prefijo bajo filtro `['auth', 'group:superadmin']` (ya existe grupo en Routes):

| Método | Ruta | Acción |
|--------|------|--------|
| GET | `/super` | Dashboard (ampliar stub) |
| GET | `/super/tenants` | Listado |
| GET | `/super/tenants/nuevo` | Form alta |
| POST | `/super/tenants/nuevo` | Crear + provisión |
| GET | `/super/tenants/(:num)` | Detalle |
| POST | `/super/tenants/(:num)` | Guardar metadatos |
| POST | `/super/tenants/(:num)/estado` | Suspender / reactivar (`activo` 0/1) |
| POST | `/super/tenants/(:num)/reset-admin` | Nueva contraseña temporal + `force_password_reset=1` |

**Menú `layouts/super`:**

```
Dashboard | Laboratorios
```

Guard: solo `superadmin`; `tenant_id` de sesión debe ser null (ya lo garantiza poblar_sesion).

---

## 4. Pantallas

### 4.1 Dashboard `/super`

- KPI: total / activos / suspendidos (ya parcialmente en stub).
- Altas recientes (últimos 7 días): nombre, fecha, estado.
- CTA: **+ Nuevo laboratorio**, enlace a listado.

### 4.2 Listado `/super/tenants`

- DataTable (o `js-datatable`): nombre, slug, contacto, plan, estado, fecha alta, acciones (Ver).
- Filtro simple: todos | activos | suspendidos (query `?estado=`).
- Buscar por nombre / slug / email (GET `q`).

### 4.3 Alta `/super/tenants/nuevo`

Campos:

**Laboratorio**

- Nombre comercial *
- Slug * (auto JS desde nombre; editable; validar unique)
- Email contacto
- Teléfono
- Plan (texto libre, placeholder “trial” / “mensual”)
- Notas internas (solo super)

**Acceso del dueño**

- Username admin * (unique global en `users.username`)
- Contraseña temporal * (mín. 8 o la regla que use Shield hoy)
- Nombre a mostrar del dueño (empleado “Dueño” / admin)

**Sucursal inicial**

- Nombre sucursal * (default: “Sucursal Principal”)
- Folio inicial (default: 1)

**Catálogo**

- [x] Sembrar catálogo base de análisis (precios General = 0)

Botones: Cancelar · **Crear laboratorio**

**Éxito (flash o vista one-shot):**

```
Laboratorio creado
URL login: …
Usuario: …
Contraseña temporal: …  [Copiar]
Avisa al dueño que al entrar deberá (más adelante) cambiar contraseña.
[Ir al detalle] [Listado]
```

La contraseña **no** se re-muestra en el detalle.

### 4.4 Detalle `/super/tenants/(:id)`

- Resumen: nombre, slug (readonly), estado badge, fechas.
- Form metadatos editables: nombre, email, teléfono, plan, notas.
- Info de provisión (solo lectura): #sucursales, username del admin principal, si tiene catálogo (count análisis).
- Acciones:
  - Suspender / Reactivar (confirm).
  - Regenerar contraseña admin (prompt o form pass nueva + mostrar una vez).

### 4.5 Suspender

- `tenant.activo = 0` → staff no entra (`poblar_sesion` + AuthFilter ya lo hacen en MT-0).
- Datos **no** se borran.
- Reactivar = `activo = 1`.

---

## 5. `TenantProvisioner` (transacción)

`app/Services/TenantProvisioner.php`

Entrada (DTO/array):

```
nombre, slug, email_contacto?, telefono?, plan?, notas_internas?,
admin_username, admin_password, admin_nombre?,
sucursal_nombre, folio_inicial = 1,
sembrar_catalogo = true
```

Pasos **en una transacción** (`transStart` / `transComplete`):

1. Insert `tenant` (`activo=1`, `onboarding_completed_at=NULL`, confirmed_at NULL).
2. Insert `sucursal` con `tenant_id` (principal).
3. Insert `consecutivo` (`idsucursal`, `tenant_id`, `consecutivo=folio_inicial`).
4. Insert `maximos` (`tenant_id`, defaults 30/10 o los actuales del producto).
5. Insert `empleado` (nombre dueño, `idsucursal`, `tenant_id`).
6. Crear user Shield: username, password, `tenant_id`, `idempleado`, `active=1`,
   `force_password_reset=1`, grupo `admin`.
7. Si `sembrar_catalogo`: copiar análisis (+ precios en 0) desde **tenant 1**
   (plantilla operativa) o desde dataset estático si se prefiere no acoplar al lab legacy.
   - **Decisión de implementación:** clonar desde `tenant_id = 1` (análisis activos
     no deleted) y crear `precios` con las 4 columnas en `0.00`. Si tenant 1 no tiene
     catálogo, no fallar la provisión: log warning y lab vacío.
8. `auditoria_log('tenant_create', 'tenant', id, …)`.

Salida:

```
tenant_id, admin_username, admin_password (plain solo en memoria/respuesta),
sucursal_id, mensaje
```

Rollback total si falla cualquier paso.

**Validaciones previas:**

- slug unique (tenant)
- username unique (users, incl. soft-deleted si aplica UNIQUE)
- password no vacío
- nombre no vacío

---

## 6. Catálogo base (detalle técnico)

No existe aún `AnalisisSeeder` en el repo; el lab actual ya tiene análisis en tenant 1.

**MT-1:** método `CatalogoPlantilla::clonarDesdeTenant(int $origen, int $destino)`:

- Lee `analisis` del origen (`deleted_at IS NULL`, opcional `activo=1`).
- Inserta en destino con nuevo `idanalisis` (auto), mismo nombre/campos/plantilla, `tenant_id=destino`.
- Inserta `precios` con General/GeneralD/Especial/EspecialD = 0.

Mapear ids viejos→nuevos no es necesario para plantilla (no hay FKs de venta al clonar).

---

## 7. Reset admin

- Localizar user: `tenant_id = X` AND grupo `admin`, preferir el de menor `id` o el ligado al empleado “dueño” de la provisión (guardar `admin_user_id` opcional en tenant en MT-1.1 si hace falta; v1: primer admin del tenant).
- Set password nueva (la que escribe super o generada en form).
- `force_password_reset = 1`.
- Mostrar pass una vez.
- Auditoría `tenant_reset_admin`.

**Nota:** la pantalla intermedia de cambio de contraseña forzado es de **onboarding**; en MT-1 solo se setea el flag. Hasta implementar onboarding, el dueño puede entrar sin forzar UI (flag queda listo).

---

## 8. Archivos a crear / tocar

| Archivo | Rol |
|---------|-----|
| `app/Services/TenantProvisioner.php` | Provisión |
| `app/Services/CatalogoPlantilla.php` (o método privado del provisioner) | Clonar catálogo |
| `app/Controllers/Super/Tenants.php` | CRUD + estado + reset |
| `app/Controllers/Super/Dashboard.php` | Ampliar KPIs + recientes |
| `app/Views/super/tenants/index.php` | Listado |
| `app/Views/super/tenants/nuevo.php` | Form alta |
| `app/Views/super/tenants/ver.php` | Detalle + acciones |
| `app/Views/super/tenants/creado.php` (opcional) | One-shot credenciales |
| `app/Views/layouts/super.php` | Menú Laboratorios |
| `app/Config/Routes.php` | Rutas |
| `app/Models/TenantModel.php` | Helpers listar/buscar |
| `tests/unit/TenantProvisionerTest.php` o test de servicio con BD | Provisión + aislamiento |
| Docs bitácora | `PLAN_ACTUALIZACION.md`, checklist MT-1 en `PLAN_MULTITENANT.md` |

Sin migraciones nuevas salvo que al implementar se decida guardar `admin_user_id` en `tenant` (opcional, no bloquea).

---

## 9. Orden de implementación

1. Ampliar `TenantModel` (listados, slug unique, buscar).
2. `TenantProvisioner` + clon de catálogo.
3. Rutas + `Super\Tenants` (nuevo, listado, ver, guardar, estado, reset).
4. Vistas + menú + dashboard.
5. JS mínimo: slug auto desde nombre en form alta.
6. Test: provisionar tenant de prueba → admin find solo su cliente; cleanup.
7. Bitácora y checklist MT-1.

---

## 10. Criterios de aceptación

1. Superadmin crea lab con todos los campos mínimos → 200 y credenciales una vez.
2. Aparece en listado; detalle muestra metadatos y conteos.
3. Admin del tenant hace login → home `/dashboard`; `session('tenant_id')` correcto.
4. Ese admin **no** ve clientes/ventas/análisis de tenant 1 (BaseTenantModel).
5. Catálogo sembrado (si checkbox): análisis con precios 0 en el nuevo tenant.
6. Suspender → login del staff rechazado con mensaje claro.
7. Reactivar → vuelve a entrar.
8. Reset admin → nueva pass funciona; la anterior no.
9. Superadmin no tiene menús de POS/lab clínico.
10. Auditoría registra create / suspend / reactivate / reset-admin.

---

## 11. Riesgos

| Riesgo | Mitigación |
|--------|------------|
| Clonar catálogo enorme lento | Solo activos; batch insert; OK en v1 (≈ decenas/centenas) |
| Username global choca | Validar before trans; mensaje claro |
| Provisión a medias | Una sola transacción |
| Super edita de más | UI y controlador solo metadatos |
| Tenant 1 sin análisis | Provisión OK sin catálogo + warning |

---

## 12. Explicitamente NO en MT-1

- Onboarding checklist / pantalla force-password (plan aparte).
- Impersonar tenant.
- Editar sucursales/catálogo del lab desde super.
- Borrado físico de tenant.
- Self-signup / pagos.
- Métricas de ventas por tenant.

---

## 13. Aprobación

Implementar MT-1 según este documento cuando el usuario diga **adelante** / **implementa MT-1**.

Si hay que ajustar algo (p. ej. no clonar desde tenant 1 sino JSON estático, o guardar `admin_user_id`), se anota aquí antes de codear.
