Docs

Sistema de Clientes

Gestión de clientes que contratan eventos en Nvito - ciclo de vida, tenant isolation, datos fiscales, asignación de responsable y vinculacion con eventos.

PublicadoMarzo 2026Equipo de desarrollo, arquitectos, stakeholders

1. Contexto de Negocio

El modelo de negocio de Nvito es servicio directo al cliente: una persona o empresa contacta a Nvito, contrata un paquete de servicios, y el equipo gestióna su evento. La entidad Client representa a la persona o empresa que contrata y paga por los servicios de evento.

Problemas que resuelve

ProblemaSolución
No se sabia quien contrato un eventoCada evento puede vincularse a un Client
No habia datos de contacto del clienteCampos email, teléfono, datos fiscales
No se podía rastrear el ciclo de vida comercialEstados: PROSPECT -> ACTIVE -> COMPLETED
No habia asignación de responsable en el equipoCampo assignedToId con FK a User
No se registraba como llego el clienteFuente de adquisicion (redes, referido, Google, etc.)
No habia datos fiscales para facturacionRFC, razon social, direccion fiscal

2. Modelo de Datos

Entidad Client

CampoTipoRequeridoDescripción
idUUID PKSiIdentificador único
organizationIdUUID FKSiOrganización propietaria (tenant)
nameVARCHAR(255)SiNombre completo o nombre comercial
emailVARCHAR(255)NoEmail de contacto (único por organización)
phoneVARCHAR(20)NoTelefono / WhatsApp
typeClientTypeSiINDIVIDUAL (default) o COMPANY
statusClientStatusSiEstado del ciclo de vida (default: PROSPECT)
rfcVARCHAR(13)NoRegistro Federal de Contribuyentes (Mexico)
businessNameVARCHAR(255)NoRazon social para facturacion
billingAddressVARCHAR(500)NoDireccion fiscal
acquisitionSourceAcquisitionSourceNoComo llego el cliente
acquisitionDetailVARCHAR(255)NoDetalle de la fuente (nombre del referido, campana, etc.)
notesTEXTNoNotas internas del equipo
assignedToIdUUID FKNoResponsable en el equipo (FK a User)
createdByUUID FKNoUsuario que creo el registro
createdAtTIMESTAMPTZSiFecha de creación
updatedAtTIMESTAMPTZSiUltima actualización
deletedAtTIMESTAMPTZNoSoft delete

Enums

ClientType:

ValorDescripción
INDIVIDUALPersona fisica
COMPANYPersona moral / empresa

ClientStatus:

ValorDescripción
PROSPECTCliente potencial, en etapa de cotizacion
ACTIVECliente con evento(s) activo(s)
COMPLETEDTodos los eventos del cliente finalizaron
INACTIVECliente inactivo o dado de baja

AcquisitionSource:

ValorDescripción
SOCIAL_MEDIARedes sociales (Instagram, Facebook, TikTok)
REFERRALReferido por otro cliente
GOOGLEBúsqueda en Google / SEO / Google Ads
WEBSITEVisita directa al sitio web
DIRECTContacto directo (networking, evento presencial)
OTHEROtra fuente

Indices

IndiceProposito
(organization_id)Clientes por organización
(status)Filtro por estado
(type)Filtro por tipo
(assigned_to_id)Clientes por responsable
(organization_id, status)Filtro compuesto eficiente

Constraint Unico

(email, organization_id) — Un cliente no puede tener el mismo email dentro de la misma organización. Los valores NULL en email no participan en la unicidad (comportamiento nativo de PostgreSQL).


3. Ciclo de Vida del Cliente

Transiciones

TransiciónTriggerContexto
PROSPECT -> ACTIVESe vincula un evento o se firma contratoManual por el equipo
PROSPECT -> INACTIVENo se concreto la ventaManual por el equipo
ACTIVE -> COMPLETEDTodos los eventos del cliente finalizaronManual por el equipo
ACTIVE -> INACTIVECancelación del servicioManual por el equipo
COMPLETED -> ACTIVENuevo evento contratadoManual por el equipo
INACTIVE -> PROSPECTEl cliente retoma contactoManual por el equipo

Nota: Las transiciones de estado son manuales (el equipo de Nvito actualiza el estado). No hay automatizacion por ahora, ya que el ciclo comercial requiere intervención humana para determinar el momento adecuado de cada transición.


4. Tenant Isolation

Cada cliente pertenece a una sola organización a traves del campo organizationId. Esto significa que:

  • Cada organización tiene su propia base de clientes — Un mismo cliente real que trabaje con dos organizaciones distintas tendrá dos registros Client independientes.
  • Las queries filtran por organización — El service filtra automáticamente por organizationId del usuario autenticado.
  • El email es único por organización — El constraint @@unique([email, organizationId]) permite que dos organizaciones tengan clientes con el mismo email.

Super Admin y organización

Cuando un Super Admin crea un cliente, debe seleccionar la organización destino mediante el OrganizationSelector. Para usuarios normales (Platform Admin, Org Owner, Org Admin), la organización se asigna automáticamente desde su contexto de autenticación.


5. Relación Client - Event

La relación entre Client y Event es 1:N con FK nullable:

Reglas de la relación

ReglaImplementación
Un cliente puede tener múltiples eventosRelación 1:N (Client.events[])
Un evento puede no tener cliente asignadoclientId es nullable en Event
Eventos existentes no se rompenLa migración no modifica datos existentes
Eliminar un cliente no elimina sus eventosonDelete: SetNull — el clientId pasa a NULL
El detalle del cliente muestra sus eventosfindOne incluye events con select básico

Vinculacion futura

La Fase 5 del plan (pendiente) conectara el selector de cliente directamente en:

  1. Formulario de creación de evento — Campo autocomplete para vincular un cliente existente
  2. Formulario de contrato — Auto-popular datos del cliente si el evento ya tiene uno vinculado
  3. Detalle del evento — Mostrar información del cliente vinculado

6. Asignación de Responsable

El campo assignedToId es una FK a User que representa al miembro del equipo responsable de la relación con el cliente.

Flujo en el Admin Panel

Reglas

  • Solo se muestran usuarios activos de la organización correspondiente
  • Al cambiar de organización (Super Admin), se limpia automáticamente el responsable seleccionado
  • Si el usuario asignado es dado de baja, el onDelete: SetNull en la FK pone assignedToId en NULL
  • El componente MemberSelector es reutilizable para otros módulos que necesiten seleccionar un miembro del equipo

7. Datos Fiscales

Los datos fiscales son opcionales y estan pensados para el contexto fiscal mexicano (CFDI):

CampoFormatoDescripción
rfcRegex: /^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$/iRFC de persona fisica (4 letras) o moral (3 letras)
businessNameVARCHAR(255)Razon social que aparece en la factura
billingAddressVARCHAR(500)Domicilio fiscal

Validación del RFC

La validación es básica (regex de formato). No incluye validación ante el SAT (Servicio de Administración Tributaria), que seria necesaria para un sistema de facturacion completo. Esto es suficiente para el MVP.

Relación con tipo de cliente

  • INDIVIDUAL: RFC de 13 caracteres (4 letras + 6 digitos + 3 alfanumericos)
  • COMPANY: RFC de 12 caracteres (3 letras + 6 digitos + 3 alfanumericos)

Los campos fiscales son opcionales independientemente del tipo de cliente, pero son particularmente relevantes para clientes de tipo COMPANY.


8. Adquisicion y Seguimiento

Fuentes de adquisicion

El campo acquisitionSource permite rastrear de donde viene cada cliente para medir la efectividad de los canales de marketing:

CanalEjemplo de detalle
SOCIAL_MEDIA"Instagram - campana bodas 2026"
REFERRAL"Referido por Maria Lopez (Boda Lopez)"
GOOGLE"Google Ads - keyword: invitaciones digitales"
WEBSITE"Formulario de contacto en nvito.mx"
DIRECT"Expo Bodas CDMX Marzo 2026"
OTHER"Contacto por WhatsApp"

El campo acquisitionDetail (VARCHAR 255) complementa la fuente con información específica como el nombre del referido, la campana publicitaria o el evento donde se conocio al cliente.

Notas internas

El campo notes (TEXT, max 2000 caracteres en validación frontend) permite al equipo registrar información relevante sobre el cliente: preferencias, historial de comunicaciones, acuerdos verbales, etc.


9. API y Permisos

Endpoints

MetodoRutaDescripciónPermiso
POST/admin/clientsCrear clienteAdminPanel
GET/admin/clientsListar clientes (páginado)AdminPanel
GET/admin/clients/:idDetalle con eventosAdminPanel
PATCH/admin/clients/:idActualizar clienteAdminPanel
DELETE/admin/clients/:idSoft deleteSuperAdmin

Guards

GuardNivelDescripción
ClerkAuthGuardClaseAutenticación JWT obligatoria
AdminPanelGuardClaseRequiere Super Admin o Platform Admin
SuperAdminGuardDeleteSolo Super Admin puede eliminar clientes

Filtros de búsqueda (GET /admin/clients)

ParametroTipoDescripción
pagenumberPágina (default: 1)
limitnumberRegistros por página (default: 10)
searchstringBusca en name, email y phone (contains, insensitive)
statusClientStatusFiltra por estado
typeClientTypeFiltra por tipo
assignedToIdUUIDFiltra por responsable
organizationIdUUIDFiltra por organización (Super Admin)

Respuesta paginada

{
  "data": [
    {
      "id": "uuid",
      "name": "Juan Perez",
      "email": "juan@example.com",
      "type": "INDIVIDUAL",
      "status": "ACTIVE",
      "assignedTo": { "id": "uuid", "firstName": "Ana", "lastName": "Garcia" },
      "_count": { "events": 2 }
    }
  ],
  "meta": {
    "total": 45,
    "page": 1,
    "limit": 10,
    "totalPages": 5
  }
}

10. Frontend (Admin Panel)

Componentes

ComponenteArchivoDescripción
ClientsTablecomponents/admin/clients-table.tsxTabla principal con filtros, páginación y acciones
ClientFormDialogcomponents/admin/client-form-dialog.tsxDialog para crear/editar con RHF + Zod
ClientStatusBadgecomponents/admin/client-status-badge.tsxBadge visual por estado
ClientTypeBadgecomponents/admin/client-type-badge.tsxBadge visual por tipo
MemberSelectorcomponents/organizations/member-selector.tsxSelector de responsable del equipo
OrganizationSelectorcomponents/organizations/organization-selector.tsxSelector de organización (Super Admin)

Hooks de React Query

HookTipoDescripción
useClients(params)QueryLista paginada con filtros
useClient(id)QueryDetalle de un cliente
useCreateClient()MutationCrear cliente + invalidar cache
useUpdateClient()MutationActualizar cliente + invalidar cache
useDeleteClient()MutationEliminar cliente + invalidar cache

Server Actions

ActionValidaciónRevalidación
createClientAction(input)createClientSchema/admin/clients
updateClientAction(id, input)updateClientSchema/admin/clients
deleteClientAction(id)Solo UUID/admin/clients

Validación Zod (client.ts)

CampoRegla
nameRequerido, max 255
emailFormato email, max 255, opcional
phoneMax 20, opcional
typeEnum ClientType, opcional
statusEnum ClientStatus, opcional
rfcRegex RFC mexicano, max 13, opcional
businessNameMax 255, opcional
billingAddressMax 500, opcional
acquisitionSourceEnum AcquisitionSource, opcional
acquisitionDetailMax 255, opcional
notesMax 2000, opcional
assignedToIdUUID valido, opcional

Referencias

Archivos fuente (Backend)

ArchivoDescripción
modules/clients/clients.controller.tsController con endpoints CRUD
modules/clients/clients.service.tsService con lógica de negocio
modules/clients/dto/create-client.dto.tsDTO de creación con class-validator
modules/clients/dto/update-client.dto.tsDTO de actualización (PartialType)
modules/clients/clients.module.tsModulo NestJS
prisma/schema.prismaModelo Client, enums, FK en Event

Archivos fuente (Frontend)

ArchivoDescripción
lib/api/types/client.tsTipos TypeScript
lib/api/services/clients.service.tsServicio API
lib/hooks/queries/use-clients.tsReact Query hooks
lib/validations/client.tsSchema Zod
lib/actions/clients.tsServer Actions
app/(dashboard)/admin/clients/page.tsxPágina principal
components/admin/clients-table.tsxTabla de clientes
components/admin/client-form-dialog.tsxFormulario dialog
components/organizations/member-selector.tsxSelector de responsable
Esta pagina fue util?