Docs

Sistema de Clientes

Tabla de Contenidos

  1. Contexto de Negocio
  2. Modelo de Datos
  3. Ciclo de Vida del Cliente
  4. Tenant Isolation
  5. Relacion Client - Event
  6. Asignacion de Responsable
  7. Datos Fiscales
  8. Adquisicion y Seguimiento
  9. API y Permisos
  10. Frontend (Admin Panel)

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 gestiona su evento. La entidad Client representa a la persona o empresa que contrata y paga por los servicios de evento.

Problemas que resuelve

ProblemaSolucion
No se sabia quien contrato un eventoCada evento puede vincularse a un Client
No habia datos de contacto del clienteCampos email, telefono, datos fiscales
No se podia rastrear el ciclo de vida comercialEstados: PROSPECT -> ACTIVE -> COMPLETED
No habia asignacion 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

CampoTipoRequeridoDescripcion
idUUID PKSiIdentificador unico
organizationIdUUID FKSiOrganizacion propietaria (tenant)
nameVARCHAR(255)SiNombre completo o nombre comercial
emailVARCHAR(255)NoEmail de contacto (unico por organizacion)
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 creacion
updatedAtTIMESTAMPTZSiUltima actualizacion
deletedAtTIMESTAMPTZNoSoft delete

Enums

ClientType:

ValorDescripcion
INDIVIDUALPersona fisica
COMPANYPersona moral / empresa

ClientStatus:

ValorDescripcion
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:

ValorDescripcion
SOCIAL_MEDIARedes sociales (Instagram, Facebook, TikTok)
REFERRALReferido por otro cliente
GOOGLEBusqueda en Google / SEO / Google Ads
WEBSITEVisita directa al sitio web
DIRECTContacto directo (networking, evento presencial)
OTHEROtra fuente

Indices

IndiceProposito
(organization_id)Clientes por organizacion
(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 organizacion. Los valores NULL en email no participan en la unicidad (comportamiento nativo de PostgreSQL).


3. Ciclo de Vida del Cliente

Transiciones

TransicionTriggerContexto
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 -> INACTIVECancelacion 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 intervencion humana para determinar el momento adecuado de cada transicion.


4. Tenant Isolation

Cada cliente pertenece a una sola organizacion a traves del campo organizationId. Esto significa que:

  • Cada organizacion tiene su propia base de clientes — Un mismo cliente real que trabaje con dos organizaciones distintas tendra dos registros Client independientes.
  • Las queries filtran por organizacion — El service filtra automaticamente por organizationId del usuario autenticado.
  • El email es unico por organizacion — El constraint @@unique([email, organizationId]) permite que dos organizaciones tengan clientes con el mismo email.

Super Admin y organizacion

Cuando un Super Admin crea un cliente, debe seleccionar la organizacion destino mediante el OrganizationSelector. Para usuarios normales (Platform Admin, Org Owner, Org Admin), la organizacion se asigna automaticamente desde su contexto de autenticacion.


5. Relacion Client - Event

La relacion entre Client y Event es 1:N con FK nullable:

Reglas de la relacion

ReglaImplementacion
Un cliente puede tener multiples eventosRelacion 1:N (Client.events[])
Un evento puede no tener cliente asignadoclientId es nullable en Event
Eventos existentes no se rompenLa migracion 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 basico

Vinculacion futura

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

  1. Formulario de creacion 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 informacion del cliente vinculado

6. Asignacion de Responsable

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

Flujo en el Admin Panel

Reglas

  • Solo se muestran usuarios activos de la organizacion correspondiente
  • Al cambiar de organizacion (Super Admin), se limpia automaticamente 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 modulos que necesiten seleccionar un miembro del equipo

7. Datos Fiscales

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

CampoFormatoDescripcion
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

Validacion del RFC

La validacion es basica (regex de formato). No incluye validacion ante el SAT (Servicio de Administracion Tributaria), que seria necesaria para un sistema de facturacion completo. Esto es suficiente para el MVP.

Relacion 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 informacion especifica 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 validacion frontend) permite al equipo registrar informacion relevante sobre el cliente: preferencias, historial de comunicaciones, acuerdos verbales, etc.


9. API y Permisos

Endpoints

MetodoRutaDescripcionPermiso
POST/admin/clientsCrear clienteAdminPanel
GET/admin/clientsListar clientes (paginado)AdminPanel
GET/admin/clients/:idDetalle con eventosAdminPanel
PATCH/admin/clients/:idActualizar clienteAdminPanel
DELETE/admin/clients/:idSoft deleteSuperAdmin

Guards

GuardNivelDescripcion
ClerkAuthGuardClaseAutenticacion JWT obligatoria
AdminPanelGuardClaseRequiere Super Admin o Platform Admin
SuperAdminGuardDeleteSolo Super Admin puede eliminar clientes

Filtros de busqueda (GET /admin/clients)

ParametroTipoDescripcion
pagenumberPagina (default: 1)
limitnumberRegistros por pagina (default: 10)
searchstringBusca en name, email y phone (contains, insensitive)
statusClientStatusFiltra por estado
typeClientTypeFiltra por tipo
assignedToIdUUIDFiltra por responsable
organizationIdUUIDFiltra por organizacion (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

ComponenteArchivoDescripcion
ClientsTablecomponents/admin/clients-table.tsxTabla principal con filtros, paginacion 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 organizacion (Super Admin)

Hooks de React Query

HookTipoDescripcion
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

ActionValidacionRevalidacion
createClientAction(input)createClientSchema/admin/clients
updateClientAction(id, input)updateClientSchema/admin/clients
deleteClientAction(id)Solo UUID/admin/clients

Validacion 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)

ArchivoDescripcion
modules/clients/clients.controller.tsController con endpoints CRUD
modules/clients/clients.service.tsService con logica de negocio
modules/clients/dto/create-client.dto.tsDTO de creacion con class-validator
modules/clients/dto/update-client.dto.tsDTO de actualizacion (PartialType)
modules/clients/clients.module.tsModulo NestJS
prisma/schema.prismaModelo Client, enums, FK en Event

Archivos fuente (Frontend)

ArchivoDescripcion
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.tsxPagina 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?