Volver a la wiki

Modelo InvitationCode — código invite-only para registro F1

Descripción

Modelo global (sin FK a Organization) que almacena códigos de invitación para el registro invite-only (/signup, PR-A #137 de F1).

Cada código otorga su plan a la organización que nace con él. La tabla vive fuera de las políticas RLS porque es consultada antes de que exista tenant alguno (el registro es público y anónimo).

Campos

CampoTipoNotas
codeCharField (unique, max_length=64)Código uppercase normalizado en save(). Ej: FOUNDER-ACME
planFK(Plan, PROTECT, related_name=“invitation_codes”)El plan que otorga este código a la org nacida. Borrar un Plan que tenga códigos falla ruidosamente (by design).
max_usesPositiveIntegerField (default=1)Número máximo de usos permitidos
uses_countPositiveIntegerField (default=0)Contador de altas realizadas con este código
expires_atDateTimeField (nullable)Caducidad. NULL = sin expiración
is_activeBooleanField (default=True)Flag de desactivación manual
notesTextField (blank=True)Notas internas para administradores (quién lo creó, para quién, etc.)
created_byFK(User, SET_NULL, related_name=“invitation_codes_created”)Admin que creó el código (trazabilidad)
created_atDateTimeField (auto_now_add=True)Timestamp de creación

Propiedades (computed)

Gestión y ciclo de vida

Creación: Actualmente via Django admin (InvitationCodeAdmin en core/admin.py). En PR-B (Settings UI) habrá una interfaz cómoda para superusers.

Validación en el alta (servicio create_signup):

  1. Código existe y activo (is_active == True)
  2. No está expirado (not is_expired)
  3. Tiene usos disponibles (has_uses_left)
  4. Selección tardía con select_for_update() para evitar race conditions en el último uso

Consumo atómico: El servicio incremente uses_count dentro de la misma transacción que crea la org. Si hay carrera por el último uso, solo uno gana; el perdedor revierte entera.

Admin mínimo

Clase InvitationCodeAdmin (core/admin.py):

No hay validación en el admin (ej. fecha de expiración en el pasado); el servidor cubre eso en la vista.

Relación con Organization

Una vez que un código se consume, la org creada almacena la FK a InvitationCode:

Organization.invitation_code = models.ForeignKey(
    InvitationCode, 
    on_delete=models.SET_NULL,  # Si se borra el código, org queda con NULL
    null=True, blank=True
)

Esta relación es de trazabilidad pura — permite saber qué código usó cada org, pero no es parte de la lógica de alta (el servicio no vuelve a consultar el código una vez que la org está creada).

Notas técnicas

Véase también

Subir