# Solución de Referencia — Ejercicio de Inmunización: Invitación a Workspace

> **Propósito:** Esta es la solución completa del ejercicio integrador del Módulo 15. Sirve como referencia para el alumno después de intentar el ejercicio por su cuenta.
> **Cómo usarlo:** Intenta primero el ejercicio (diagramar el sistema de invitaciones desde el spec). Luego compara tu solución con esta, diagrama por diagrama — las diferencias te muestran qué ambigüedades se te escaparon.

---

## Spec Simulado (Contexto)

**Feature:** Sistema de Invitación a Workspace
**Spec ID:** SPEC-20260603-01
**Autor:** Product Operator

**Contexto:**
Nuestra plataforma SaaS colaborativa permite a equipos trabajar en espacios compartidos llamados "workspaces". Actualmente, los administradores pueden agregar miembros manualmente, pero esto requiere que el administrador conozca el email del usuario y que el usuario ya tenga una cuenta en la plataforma. Para escalar la adopción, necesitamos un sistema de invitación que permita a cualquier administrador invitar a personas externas (con o sin cuenta) mediante un link enviado por email.

**Comportamiento descrito originalmente:**
> Un usuario administrador puede invitar a nuevos miembros a su workspace ingresando su email. El sistema envía un email con un link de invitación válido por 7 días. Si el invitado ya tiene cuenta, al hacer clic en el link se une al workspace automáticamente. Si no tiene cuenta, debe registrarse primero y luego se une. El administrador puede revocar invitaciones pendientes. Si la invitación expira, se marca como expirada y el administrador puede reenviarla. Un email puede tener máximo 3 invitaciones activas simultáneas; si ya hay 3 y se intenta invitar de nuevo, el sistema rechaza la operación.

---

## Diagrama 1: Flowchart — Flujo Completo

```mermaid
graph TD
    A[Admin en configuración] --> B[Abre modal Invitar]
    B --> C[Ingresa email]
    C --> D[Email válido?]
    D -->|No| E[Mostrar: "Email inválido"]
    E --> C
    D -->|Sí| F{Bloqueado por rate limit?}
    F -->|Sí| G[Mostrar: "Máximo 3 invitaciones activas por email"]
    G --> C
    F -->|No| H[CREAR invitación: estado Pendiente]
    H --> I[Generar token único]
    I --> J[Guardar en DB: email, token, workspace_id, expira_en]
    J --> K[Enviar email con link /accept?token=XXX]

    subgraph "Flow del Invitado"
        L[Invitado recibe email] --> M[Abre link]
        M --> N{Token válido?}
        N -->|No| O[Mostrar: "Link inválido o expirado"]
        N -->|Sí| P{Invitación revocada?}
        P -->|Sí| O
        P -->|No| Q{Usuario tiene cuenta?}
        Q -->|Sí| R[Iniciar sesión / ya logueado]
        R --> S[Unir al workspace]
        S --> T[Marcar invitación como Aceptada]
        T --> U[Mostrar: "Bienvenido al workspace"]
        Q -->|No| V[Redirigir a registro]
        V --> W[Usuario se registra]
        W --> S
    end

    K --> L

    subgraph "Admin — Gestión"
        X[Admin ve lista de invitaciones] --> Y{Pendiente?}
        Y -->|Sí| Z[Botón Revocar]
        Y -->|No| AA[Botón Reenviar si expirada]
        Z --> AB{Marcar como Revocada}
        AB --> AC[Token ya no es válido]
        AA --> AD[Crear nueva invitación con nuevo token]
        AD --> K
    end
```

### Preguntas de Ambigüedad que el Diagrama Revela

Preguntas que el texto original no respondía y que el diagrama obligó a responder:

| # | Ambigüedad Detectada | Decisión Tomada |
|---|---------------------|----------------|
| 1 | ¿Qué pasa si el email ingresado no tiene formato válido? | Se valida en frontend y backend. Si es inválido, se muestra error y se permite corregir sin perder el resto del formulario. |
| 2 | ¿Qué pasa si el token de invitación es manipulado (tampered)? | El token es un UUID v4 firmado con HMAC. Si no pasa verificación, se muestra "Link inválido". |
| 3 | ¿El rate limit de 3 invitaciones activas es por email o por email+workspace? | Es por email+workspace. Un mismo email puede tener 3 invitaciones activas a workspaces diferentes. |
| 4 | ¿Qué pasa si el usuario recibe la invitación, no tiene cuenta, se registra, pero el token expiró durante el registro? | Al completar el registro, el sistema verifica el token nuevamente. Si expiró, se muestra "Invitación expirada. Solicita una nueva." |
| 5 | ¿Qué pasa si el administrador revoca una invitación mientras el invitado está en medio del registro? | La revocación invalida el token en DB. Al completar el registro, el token ya no es válido y se muestra el mensaje de link inválido. |
| 6 | ¿El reenvío genera un nuevo token o reutiliza el existente? | Genera un nuevo token con nueva fecha de expiración (7 días desde el reenvío). La invitación anterior se marca como reemplazada. |

---

## Diagrama 2: State Diagram — Ciclo de Vida de una Invitación

```mermaid
stateDiagram-v2
    [*] --> Pendiente: Admin ingresa email
    Pendiente --> Aceptada: Invitado hace clic y tiene cuenta
    Pendiente --> Aceptada: Invitado se registra y hace clic
    Pendiente --> Revocada: Admin hace clic en Revocar
    Pendiente --> Expirada: Pasan 7 días
    Pendiente --> Reemplazada: Admin hace clic en Reenviar
    Reemplazada --> [*]
    Revocada --> [*]
    Expirada --> Pendiente: Admin hace clic en Reenviar
    Aceptada --> [*]
```

### Tabla de Transiciones Detallada

| Estado Actual | Evento | Condición | Siguiente Estado | Acción / Efecto |
|--------------|--------|-----------|-----------------|-----------------|
| — | Admin crea invitación | Email válido ∧ rate limit OK | Pendiente | Enviar email con token. Iniciar TTL de 7 días. |
| Pendiente | Invitado acepta | Token válido ∧ usuario logueado | Aceptada | Unir usuario al workspace. Enviar notificación al admin. |
| Pendiente | Invitado se registra y acepta | Token válido ∧ registro completado | Aceptada | Crear cuenta + unir al workspace. Enviar bienvenida. |
| Pendiente | Admin revoca | — | Revocada | Invalidar token. Enviar email al invitado: "Invitación revocada". |
| Pendiente | TTL expira | 7 días sin aceptar | Expirada | Job nocturno marca como expirada. Liberar slot del rate limit. |
| Pendiente | Admin reenvía | — | Reemplazada | Nueva invitación creada (nuevo token). Esta se marca como reemplazada (histórico). |
| Expirada | Admin reenvía | — | Pendiente | Nueva invitación creada (nuevo token, nuevo TTL de 7 días). |
| Aceptada | — | — | [*] | Estado terminal. No hay transiciones salientes. |
| Revocada | — | — | [*] | Estado terminal. No hay transiciones salientes. |
| Reemplazada | — | — | [*] | Estado terminal. Solo existe como registro histórico. |

### Estados Huérfanos Detectados

- **Pendiente → ?:** ¿Qué pasa si el admin elimina el workspace mientras hay invitaciones pendientes? Decisión: Se marcan como Revocadas automáticamente.
- **Aceptada → ?:** ¿Se puede expulsar a un miembro que entró por invitación? Decisión: Sí, pero es otra feature (gestión de miembros, no de invitaciones). No entra en este spec.
- **Pendiente → Pendiente:** ¿Qué pasa si el usuario intenta aceptar con un token válido pero el workspace está en "suspended" por falta de pago? Decisión: Se muestra "El workspace no está activo. Contacta al administrador."

---

## Diagrama 3: Sequence Diagram — Coreografía entre Sistemas

```mermaid
sequenceDiagram
    participant Admin
    participant FE_Admin
    participant BE_Invitaciones
    participant DB
    participant EmailService

    Admin->>FE_Admin: Abre modal Invitar
    FE_Admin->>BE_Invitaciones: GET /api/workspace/{id}/invitations
    BE_Invitaciones->>DB: SELECT invitaciones activas
    DB-->>BE_Invitaciones: Lista
    BE_Invitaciones-->>FE_Admin: 200 + lista
    FE_Admin-->>Admin: Muestra UI

    Admin->>FE_Admin: Ingresa email + clic Invitar
    FE_Admin->>BE_Invitaciones: POST /api/workspace/{id}/invitations
    BE_Invitaciones->>DB: COUNT active invitations for email+workspace
    DB-->>BE_Invitaciones: count
    alt count >= 3
        BE_Invitaciones-->>FE_Admin: 429 "Rate limit excedido"
        FE_Admin-->>Admin: Muestra error
    else count < 3
        BE_Invitaciones->>DB: INSERT invitation (uuid, email, workspace_id, expira_en)
        DB-->>BE_Invitaciones: OK
        BE_Invitaciones->>EmailService: send(email, template="invite", data={token, workspace})
        EmailService-->>BE_Invitaciones: sent
        BE_Invitaciones-->>FE_Admin: 201 Created
        FE_Admin-->>Admin: "Invitación enviada"
    end
```

---

## Trazabilidad: Diagrama → Tareas

Cada rama del flowchart se traduce a tareas del backlog:

| Rama del Diagrama | Tarea Propuesta | Tipo | Esfuerzo |
|------------------|----------------|------|----------|
| Admin ingresa email → validación | `[FE] Validación de email en frontend (formato + rate limit check)` | Validación | S |
| Email válido → rate limit check | `[BE] Endpoint GET /api/workspace/{id}/invitations/active-count` | Feature | XS |
| Rate limit > 3 → mostrar error | `[FE] Manejo de error 429 con mensaje claro` | Error | XS |
| Rate limit OK → crear invitación | `[BE] Endpoint POST /api/workspace/{id}/invitations` | Feature | M |
| Crear invitación → generar token | `[BE] Generación de UUID v4 + HMAC para token de invitación` | Feature | S |
| Guardar en DB | `[BE] Migración: tabla workspace_invitations (id, token, email, workspace_id, status, created_at, expires_at)` | Infra | S |
| Enviar email | `[BE] Integración con proveedor de email + template de invitación` | Feature | M |
| Invitado abre link → validar token | `[BE] Endpoint GET /api/invitations/accept?token={token}` | Feature | M |
| Token inválido → mostrar error | `[FE] Página de "Link inválido o expirado"` | Error | XS |
| Token válido + tiene cuenta → unir | `[BE] Lógica de unión: INSERT workspace_members` | Feature | S |
| Token válido + no tiene cuenta → registro | `[FE Página de registro con token en URL` | Feature | M |
| Registro completado → unir al workspace | `[BE] Flujo post-registro: verificar token + unir` | Feature | S |
| Admin revoca → marcar como revocada | `[BE] Endpoint PATCH /api/invitations/{id}/revoke` | Feature | S |
| Invitación expira → job nocturno | `[BE] Cron job: marcar expiradas + liberar rate limit` | Feature | S |
| Admin reenvía → nueva invitación | `[BE] Endpoint POST /api/invitations/{id}/resend` | Feature | M |
| Admin ve lista de invitaciones | `[FE] Tabla de invitaciones con estado, fecha, acciones` | Feature | M |

---

## Auto-Evaluación según Rúbrica

| Criterio | Evaluación | Justificación |
|----------|-----------|---------------|
| **1. Cobertura de caminos (feliz, error, borde)** | **Sobresaliente (2)** | Happy path completo (invitar → aceptar con/sin cuenta). Errores: email inválido, rate limit, token inválido, token expirado, invitación revocada. Bordes: concurrencia (revocar mientras registra), reemplazo de token, workspace suspendido, eliminación de workspace. |
| **2. Diagrama auto-contenido** | **Sobresaliente (2)** | Los 3 diagramas tienen etiquetas explícitas en todas las flechas, decisiones con condiciones, y no dependen del spec textual para entenderse. Un ingeniero puede implementar solo con los diagramas + tabla de transiciones. |
| **3. Trazabilidad a tareas** | **Sobresaliente (2)** | 15 tareas mapeadas 1:1 con ramas del diagrama. Cada tarea tiene tipo y esfuerzo estimado. No hay tareas huérfanas ni ramas sin tarea. |

**Resultado: 6/6 — Inmunizado.**

---

## Notas Adicionales

### Decisiones de Producto que Surgieron del Proceso de Diagramación

1. **Rate limit por email+workspace (no global):** Un mismo email puede tener invitaciones activas a diferentes workspaces. Esto evita bloquear a un usuario que legítimamente está siendo invitado a múltiples equipos.

2. **Reenvío genera nuevo token:** Por seguridad. Si el token original fue expuesto (email interceptado), el reenvío invalida el anterior. Esto también extiende el TTL a 7 días desde el reenvío.

3. **Estados terminales explícitos:** Aceptada, Revocada y Reemplazada son estados terminales. No se puede "revocar" una invitación ya aceptada —para eso existe la gestión de miembros del workspace.

4. **Sin notificación push al admin:** Decisión deliberada. El admin puede ver el estado en la tabla de invitaciones. Una notificación en tiempo real por cada aceptación sería ruido. Se envía email de resumen diario si hay actividad.

### Lecciones Aprendidas

- **El rate limit apareció solo al diagramar.** En el texto original, "máximo 3 invitaciones activas" era una línea. En el diagrama, se convirtió en 3 nodos + 1 tarea. Visualizar la condición obligó a definir qué pasa cuando se excede.
- **El caso de revocación durante registro no existía en el texto.** El texto decía "el admin puede revocar" y "el invitado sin cuenta se registra". El diagrama reveló que ambos eventos pueden ocurrir simultáneamente.
- **El job nocturno de expiración es invisible en el texto.** El texto dice "si la invitación expira" como si fuera mágico. El diagrama obligó a preguntar: ¿quién marca la expiración? ¿Un cron? ¿En el momento en que el usuario hace clic?

---

*Solución de referencia — Módulo 15, Bootcamp Product Operator*
