Estructura del Proyecto
nvito-admin es una aplicación Next.js 16 con App Router que sirve como panel de administración para organizadores de eventos. Es el frontend principal donde los usuarios gestiónan eventos, invitados, invitaciones, mesas, RSVP, comunicaciones y más.
Stack Tecnologico
| Tecnologia | Versión | Proposito |
|---|---|---|
| Next.js | 16.1.6 | Framework React con App Router |
| React | 19.2.3 | Libreria de UI |
| TypeScript | ^5 | Tipado estático |
| Tailwind CSS | v4 | Estilos utilitarios |
| @clerk/nextjs | ^6.37.1 | Autenticación |
| @tanstack/react-query | ^5.90.20 | Cache y fetching de datos |
| SWR | ^2.4.0 | Data fetching reactivo |
| React Hook Form | ^7.71.1 | Gestión de formularios |
| Zod | ^4.3.6 | Validación de esquemas |
| shadcn/ui + Radix UI | Multiples | Componentes de UI |
| Recharts | ^3.7.0 | Graficas y visualizaciones |
| Framer Motion | ^12.33.0 | Animaciones |
| next-themes | ^0.4.6 | Modo oscuro |
Estructura de Directorios
Estructura de Directorios
Mapa de Rutas
El siguiente diagrama muestra todas las rutas de la aplicación organizadas por grupo de layout:
Mapa de Rutas — nvito-admin
Descripción de Rutas
| Ruta | Descripción |
|---|---|
/sign-in | Inicio de sesion via Clerk (catch-all) |
/sign-up | Registro de nuevos usuarios via Clerk |
/ (dashboard) | Panel principal con estadisticas generales |
/events | Lista de todos los eventos de la organización |
/events/new | Wizard de creación de evento (paso a paso) |
/events/[eventId] | Dashboard del evento con metricas |
/organization | Configuración de la organización actual |
/admin/clients | Gestión de clientes que contratan eventos. CRUD completo con filtros por estado, tipo y responsable |
/admin/* | Panel de administración de plataforma (Super Admin + Platform Admin). Las acciones destructivas (eliminar usuarios/orgs/invitaciones) son exclusivas de Super Admin |
Sub-rutas de Evento
Cada evento (/events/[eventId]/) expone 21 sub-rutas que cubren la gestión completa:
| Sub-ruta | Modulo | Descripción |
|---|---|---|
/invitations | Invitaciones | Gestión de invitaciones: Editor Visual Artisan (internas) o gestión HTML (externas) |
/guests | Invitados | Lista de invitados, importación Excel, grupos |
/rsvps | Confirmaciones | Respuestas de invitados, estadisticas RSVP |
/tables | Mesas | Asignación de mesas con drag-and-drop (dnd-kit) |
/check-in | Check-in | Registro de asistencia día del evento |
/qr-passes | Pases QR | Generación y gestión de códigos QR por invitado |
/mobile-access | Acceso App Movil | Códigos de acceso HOST para la app movil (max 3 activos) |
/gift-registry | Mesa de regalos | Configuración de listas de regalos y cuentas bancarias |
/locations | Ubicaciones | Sedes del evento con coordenadas |
/accommodation | Hospedaje | Opciones de alojamiento para invitados |
/itinerary | Itinerario | Agenda del evento con horarios |
/background-music | Música | Configuración de música de fondo para la invitación |
/collaborators | Colaboradores | Gestión de acceso por evento |
/services | Servicios | Servicios contratados para el evento |
/communications | Comunicaciones | Envio multicanal (5 tipos de campana), templates, workflows, historial con filtros, estadisticas y chatbot WhatsApp |
/analytics | Analitica | Metricas de vistas, RSVP, engagement |
/media | Media Center | Galería de fotos, subidas de invitados, audio guestbook (con moderación) y mensajes de felicitación — con tabs, paginación server-side y grids compactos |
/custom-domain | Dominio | Configuración de dominio personalizado |
/edit | Editar | Edicion de datos generales del evento |
Bloqueo en Estados Terminales
Los eventos en estado CANCELLED, COMPLETED, EXPIRED (visual) o con fecha pasada bloquean todas las acciones de modificación en el admin dashboard. El EventContext expone un flag centralizado isTerminalState que las 14 páginas de evento consultan para deshabilitar interacciones.
Expirado es un estado visual (no de BD) que se calcula cuando
COMPLETED+activatedAt === null— el evento nunca público su invitación y la fecha ya paso.
Comportamiento en estado terminal:
- ServiceCards: Muestran "Bloqueado" en vez de link navegable
- DisabledServiceCards: No permiten activar servicios nuevos
- Acciones rápidas: Solo muestran "Ver Estadisticas"
- Alertas inteligentes: No se generan en estado terminal
- Botón "Nueva Invitación": Completamente deshabilitado (sin link navegable)
- Formularios de edicion: Deshabilitados con mensaje informativo
Cancelación de eventos:
La cancelación ahora incluye un campo obligatorio cancellationReason que se muestra en la tarjeta de estado del dashboard del evento. La respuesta GET /events/:id incluye los campos cancellationReason, cancelledAt, activatedAt y completedAt. Tras cancelar, la UI se actualiza inmediatamente via invalidación del cache de React Query.
Componentes Clave
Event Wizard
El wizard de creación de eventos (/events/new) guia al usuario a traves de un flujo multi-paso para configurar su evento. Incluye seleccion de tipo de evento, datos básicos, ubicaciones y plantilla de invitación.
Editor Visual Artisan (iframe-based)
El editor visual Artisan permite a los usuarios personalizar sus invitaciones de forma interactiva. Se integra mediante un iframe que carga el editor con comunicación bidireccional entre el admin y el editor. El hook use-artisan-invitation-editor.ts gestióna el estado y la comunicación con el iframe.
Invitaciones Externas
El módulo de invitaciones soporta dos origenes (InvitationSource): INTERNAL (editor visual + IA) y EXTERNAL (HTML subido por el usuario). La página de creación (/invitations/new) muestra 2 opciones al mismo nivel: "Elegir Template" y "Cargar HTML Existente".
Componentes principales:
ExternalUploadForm— Formulario drag & drop para subir archivo.html/.htm(max 5 MB). Campos opcionales: SEO title y description. Valida extension y tamano en cliente antes de enviar.ExternalInvitationDetail— Vista de detalle para invitaciones externas (early return, sin editor visual). Muestra preview en iframe, estado con badge naranja "Externa", y acciones contextuales según el estado.ReuploadExternalDialog— Dialog modal para reemplazar el HTML (solo en estadoIN_CONSTRUCTION). Advierte que la operación es irreversible.DownloadExternalButton— Descarga el HTML original desde la BD como archivo.html.InvitationsTable— Muestra badge naranja "Externa" cuandosource === EXTERNAL.
Acciones disponibles por estado:
| Estado | Acciones |
|---|---|
IN_CONSTRUCTION | Descargar HTML, Re-subir HTML, Publicar |
PUBLISHED | Descargar HTML, Ver publicada, Despublicar, Cerrar |
CLOSED | Descargar HTML, Reabrir |
Flujo de datos:
- Service:
external-invitations.ts(3 métodos: upload, reupload, download) - Actions:
external-invitations.ts(3 server actions con validación Zod) - Schemas:
uploadExternalInvitationSchema,reuploadExternalInvitationSchemaeninvitation.ts
Personalización por Invitado (Editor)
El editor de invitaciones incluye controles para configurar la personalización por invitado:
- Loader Editor (
loader-editor): Permite configurar el texto de bienvenida personalizado que se muestra en la pantalla de carga. Incluye un campo para el template del saludo (con placeholder{guestName}) y un campo para el texto de fallback generico que se muestra cuando no hay shortCode. - RSVP Fields (
rsvp-fields): Permite configurar el saludo personalizado de la sección RSVP. Incluye campos para el template de saludo con nombre (ej: ", confirma tu asistencia") y el texto de fallback.
Los textos configurados se almacenan en el schema de la invitación y se inyectan en el HTML generado como parte del JavaScript de personalización. Los tipos TypeScript correspondientes estan en lib/api/types/invitation.ts.
RSVP Analytics (Recharts)
La sección de analitica utiliza Recharts para mostrar graficas de:
- Tendencias de confirmaciones en el tiempo
- Distribucion de respuestas (confirmados / pendientes / rechazados)
- Vistas de la invitación por dispositivo y navegador
- Timeline de actividad
Gestión de Clientes (Admin)
La página /admin/clients permite gestionar la relación comercial con los clientes que contratan eventos. Es accesible para usuarios con permisos de Admin Panel (Super Admin y Platform Admin).
Componentes principales:
ClientsTable— Tabla con páginación server-side, filtros por estado (ClientStatus), tipo (ClientType), búsqueda por nombre/email/teléfono, y ordenamiento en columnas. Incluye columna de organización visible para Super Admin.ClientFormDialog— Dialog con React Hook Form + Zod para crear y editar clientes. Secciones: contacto, clasificación, datos fiscales (colapsable), adquisicion, asignación de responsable y notas.MemberSelector— Selector de miembros activos de una organización para asignar un responsable (assignedToId). Carga usuarios viaGET /admin/users?organizationId=x.OrganizationSelector— Selector de organización para Super Admin (elige tenant al crear un cliente).ClientStatusBadge/ClientTypeBadge— Badges visuales para estado y tipo de cliente.
Flujo de datos:
- Hooks:
useClients(params),useClient(id),useCreateClient(),useUpdateClient(),useDeleteClient() - Server Actions:
createClientAction,updateClientAction,deleteClientAction - Validación: schema Zod
createClientSchemacon validación de RFC mexicano (/^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$/i)
Gestión de Mesas (dnd-kit)
El módulo de mesas usa @dnd-kit (core + sortable) para permitir arrastrar y soltar invitados entre mesas, con vista visual del layout.
Comunicaciones (/communications)
La página de comunicaciones es una de las más completas del admin, organizada en 5 tabs:
1. Enviar Mensaje — 5 cards de acción que abren wizards multi-paso:
| Card | Tipo de campana | Descripción |
|---|---|---|
| Enviar Invitación | invitation_send | Envia el link de la invitación digital |
| Recordatorio RSVP | rsvp_reminder | Recordatorio para invitados pendientes |
| Save the Date | save_the_date | Anuncio visual con imagen del evento |
| RSVP por WhatsApp | whatsapp_rsvp_chatbot | Confirmación automatizada via chatbot |
| Mensaje Personalizado | custom_message | Comunicado libre del organizador |
Cada wizard incluye: seleccion de canal, filtrado avanzado de destinatarios, estimacion en tiempo real, preview del mensaje, detección de duplicados y confirmación final. El componente CampaignWizardShell proporciona una shell reutilizable con prop disabledHint para indicar al usuario por que no puede avanzar al siguiente paso.
2. Historial — Lista de campanas con filtros client-side:
- Filtros: tipo de campana, estado, canal, búsqueda por nombre
- Columnas ordenables: fecha, nombre, tipo, estado, destinatarios, enviados
- Vista detalle con 4 stat cards (destinatarios, enviados, entregados, fallidos) y porcentajes
- Tabla de mensajes individuales con status badges y botón de retry para fallidos
- Reutiliza componentes estandar:
useTableFilters,useSortableTable,FilterBar,SortableHeader,EmptyState
3. Estadisticas — Dashboard con metricas agregadas:
- Cards principales: Enviados, Entregados, Abiertos, Fallidos (con porcentajes)
- Desglose por canal (Email, WhatsApp)
- Desglose por tipo de campana
4. Plantillas — Gestión de templates de comunicación:
- CRUD con editor WYSIWYG basado en Tiptap (toolbar, variables, image placeholder)
- Filtros por tipo de campana y estado
- Acciones: archivar, marcar como default
- 12 variables disponibles (nombre, evento, fecha, hora, ubicación, etc.)
5. Automatizacion — Workflows automáticos:
- 6 triggers: GUEST_ADDED, RSVP_CONFIRMED, RSVP_DECLINED, DAYS_BEFORE_EVENT, EVENT_DAY, POST_EVENT
- Toggle activo/pausado con Switch
- Conteo de ejecuciones y última ejecución
- Crear, editar, eliminar workflows
Componentes (20+ archivos en src/components/communications/):
- 5 dialog wizards:
send-invitation-dialog,send-reminder-dialog,send-message-dialog,send-save-the-date-dialog,send-rsvp-chatbot-dialog - Listas y detalle:
campaign-list,campaign-detail,communication-stats - Templates:
template-list,template-editor-dialog,template-selector - Workflows:
workflow-list,workflow-create-dialog - Componentes compartidos:
recipient-selector-advanced,guest-search-selector,channel-selector,message-preview,send-confirmation-step,image-upload-field,campaign-wizard-shell - Editor rico:
template-rich-editor/(subdirectorio con 5 archivos: editor, toolbar, hooks, extensions)
Flujo de datos:
- Services:
communications.service.ts(19 métodos),communication-templates.service.ts(7 métodos) - Hooks:
use-communications.ts(5 queries + 3 mutations),use-communication-templates.ts(2 queries + 4 mutations) - Actions:
communications.ts(13 server actions con validación Zod) - Validación:
communication.ts(16 schemas Zod),communication-template.ts - Types:
communication.ts(352 lineas — campanas, mensajes, workflows, DTOs),communication-template.ts(78 lineas)
Arquitectura del API Client
El sistema de comunicación con el backend sigue una arquitectura en capas claramente separada:
Arquitectura del API Client
ApiClient (client.ts)
Clase base que encapsula todas las operaciones HTTP:
- Metodos:
get,post,put,patch,delete,upload - Autenticación: Token Bearer opcional en cada request
- Base URL: Configurable via
NEXT_PUBLIC_API_URL(default:http://localhost:3000/v1) - Error handling: Clase
ApiErrorpersonalizada constatusCodeymessage - Cache: Deshabilitado para API calls (
cache: 'no-store') - Upload: Soporte para
multipart/form-datasin Content-Type header (lo genera el browser)
Services (33 archivos)
Cada dominio tiene un archivo de servicio dedicado que encapsula los endpoints:
| Servicio | Archivo | Endpoints |
|---|---|---|
| Eventos | events.ts | CRUD de eventos |
| Invitados | guests.service.ts | CRUD invitados, importación Excel |
| Invitaciones | invitations.ts | CRUD invitaciones, publicación |
| RSVPs | rsvps.service.ts | Lista y stats de confirmaciones |
| Mesas | tables.service.ts | CRUD mesas, asignaciones |
| Ubicaciones | locations.service.ts | CRUD ubicaciones |
| Itinerario | itinerary.service.ts | CRUD actividades |
| Mesa de regalos | gift-registry.service.ts | Config, items, cuentas |
| Música | background-music.service.ts | Config de música |
| Hospedaje | accommodation.service.ts | Config de alojamiento |
| QR Passes | qr-passes.service.ts | Generación y gestión QR |
| Acceso Movil | mobile-access.service.ts | Códigos de acceso HOST |
| Comunicaciones | communications.service.ts | Campanas, mensajes, preview, estimacion |
| Templates de comunicación | communication-templates.service.ts | Templates CRUD, defaults |
| Analitica | analytics.service.ts | Metricas y stats |
| Media | media.service.ts | Galería, uploads |
| Organización | organization.service.ts | Config org actual |
| Organizaciones (admin) | organizations.service.ts | CRUD orgs (super admin) |
| Admin | admin.service.ts | Operaciones de super admin |
| Roles | roles.service.ts | Gestión de roles |
| Paquetes | packages.service.ts | Planes y paquetes |
| Ventas | sales.service.ts | Reportes de ventas |
| Contratos | contracts.service.ts | Gestión de contratos |
| Clientes | clients.service.ts | CRUD de clientes |
| Templates (admin) | templates-admin.service.ts | Templates del sistema |
| Salud | health.service.ts | Health checks |
| Tipos de evento | event-types.ts | Catálogo de tipos |
| Servicios de evento | event-services.service.ts | Servicios contratados |
| Generación IA | ai-generation.service.ts | Generación con IA |
| Invitaciones externas | external-invitations.ts | Upload, re-upload, download HTML |
| Dominio personalizado | (dentro de invitations) | Config de dominio |
React Query Hooks (39 hooks)
Ubicados en src/lib/hooks/queries/, cada hook encapsula:
- Queries (
useQuery): Lectura de datos con cache automático - Mutations (
useMutation): Operaciones de escritura con invalidación de cache - Query Keys: Sistema centralizado en
keys.tspara invalidación predecible
Server Actions
Arquitectura de Server Actions
El proyecto utiliza Next.js Server Actions como capa de mutacion entre el frontend y la API. Todas las acciones del servidor siguen un patrón consistente:
- Autenticación: Obtienen el token de Clerk via
auth().getToken() - Validación: Validan inputs con schemas Zod antes de llamar a la API
- Ejecución: Llaman al API Client con el token de autenticación
- Resultado: Retornan
ActionResult<T>tipado (discriminated union) - Revalidación: Ejecutan
revalidatePath()para actualizar cache de Next.js
ActionResult (Error Handling)
Todas las server actions retornan un tipo ActionResult<T> que es una discriminated union:
type ActionResult<T> =
| { success: true; data: T }
| { success: false; error: string };
Esto garantiza que el consumer siempre maneje ambos casos (exito y error) de forma type-safe, sin excepciones no controladas.
Server Actions Disponibles (17 archivos)
| Archivo | Dominio | Acciones |
|---|---|---|
events.ts | Eventos | CRUD, cancelación, reapertura |
guests.ts | Invitados | CRUD, importación Excel |
invitations.ts | Invitaciones | CRUD, publicación, estados |
users.ts | Usuarios | Gestión, roles |
organizations.ts | Organizaciones | Configuración |
communications.ts | Comunicaciones | Campanas, envio masivo |
media.ts | Media | Upload (presigned URL), confirmación, eliminación |
tables.ts | Mesas | CRUD, asignaciones |
locations.ts | Ubicaciones | CRUD |
itinerary.ts | Itinerario | CRUD, reordenamiento |
gift-registry.ts | Mesa de regalos | Configuración, items |
accommodation.ts | Hospedaje | Configuración |
qr-passes.ts | Pases QR | Generación |
background-music.ts | Música | Configuración |
collaborators.ts | Colaboradores | Invitaciones, gestión |
clients.ts | Clientes | CRUD, crear, actualizar, eliminar |
external-invitations.ts | Inv. externas | Upload HTML, re-upload, descarga HTML original |
Todas las acciones tienen validación Zod en la frontera y tests unitarios co-localizados.
Gestión de Estado
React Query 5 + SWR
La aplicación usa un enfoque hibrido para gestión de estado del servidor:
- React Query 5 (
@tanstack/react-query): Se usa como sistema principal para la mayoria de queries y mutations - SWR: Se usa en casos puntuales de revalidación reactiva (tablas, invitaciones)
Query Keys Factory
El archivo keys.ts centraliza todas las claves de cache siguiendo el patrón de factory:
// Ejemplo de estructura de keys
queryKeys.guests.all(eventId) // ['guests', eventId]
queryKeys.guests.list(eventId, params) // ['guests', eventId, 'list', params]
queryKeys.guests.detail(eventId, id) // ['guests', eventId, id]
Dominios cubiertos por el Query Key Factory (19 dominios):
admin(users, roles, invitations, health)contracts,clients,packages,organizations,salesguests,guestGroups,tables,qrPassescommunications,analytics,rsvps,mediainvitations,organization,permissionsevents,dashboard,giftRegistry,backgroundMusicaccommodation,locations,itinerarycollaborators,team,customDomaineventTypes,templates,templatesAdminmobileAccess
Invalidación de Cache
Al completar una mutation, se invalidan selectivamente las queries relacionadas usando queryClient.invalidateQueries() con las keys del factory. Esto asegura que los datos mostrados siempre estan actualizados.
Validación de Formularios
React Hook Form + Zod
Todos los formularios usan React Hook Form con Zod como resolver de validación a traves de @hookform/resolvers.
Esquemas de Validación (16 archivos)
| Esquema | Archivo | Campos principales |
|---|---|---|
| Evento | event.ts | nombre, tipo, fecha, descripción |
| Invitado | guest.ts | nombre, email, teléfono, grupo |
| Invitación | invitation.ts | slug, template, configuración |
| Ubicación | location.ts | nombre, direccion, coordenadas |
| Itinerario | itinerary.ts | título, hora inicio/fin, descripción |
| Media | media.ts | archivo, tipo, tamano, eventId |
| QR Pass | qr-pass.ts | invitado, tipo de pase |
| Acceso Movil | mobile-access.ts | expiresAt (crear), isActive (actualizar), MAX_ACTIVE_CODES |
| Usuario | user.ts | nombre, email, rol |
| Organización | organization.ts | nombre, slug, configuración |
| Contrato | contract.ts | organización, paquete, fechas |
| Mesa | table.ts | nombre, capacidad, zona |
| Servicios de evento | event-services.ts | proveedor, costo, notas |
| Comunicaciones | communication.ts | eventId, campaignId, parametros |
| Cliente | client.ts | nombre, email, teléfono, tipo, estado, RFC, datos fiscales, fuente de adquisicion |
Todos los esquemas tienen tests unitarios co-localizados en __tests__/.
Sistema de UI
shadcn/ui (41+ componentes)
La aplicación utiliza shadcn/ui como sistema de componentes base, construido sobre Radix UI para accesibilidad y Tailwind CSS v4 para estilos.
Componentes UI Disponibles
| Categoría | Componentes |
|---|---|
| Layout | Card, Separator, Scroll Area, Tabs, Collapsible |
| Formularios | Input, Textarea, Select, Checkbox, Switch, Radio Group, Slider, Calendar, Money Input |
| Feedback | Alert, Alert Dialog, Dialog, Tooltip, Popover, Sonner (toasts), Progress, Status Alert |
| Navegación | Dropdown Menu, Command (cmdk), Badge |
| Datos | Table, Skeleton, Table Skeleton, Empty State, Filter Bar, Search Input, Sortable Header |
| Acciones | Button, Label, Avatar, Highlight Text, Time Ago, Package Badge |
| Overlay | Alert Dialog, Dialog, Popover, Dropdown Menu |
Componentes Custom
Además de shadcn/ui, el proyecto incluye componentes personalizados:
filter-bar.tsx: Barra de filtros reutilizable para tablassearch-input.tsx: Input de búsqueda con debounceservice-page-header.tsx: Header estandar para páginas de serviciotable-skeleton.tsx: Skeleton loader para tablasempty-state.tsx: Estado vacio con icono y acciónsortable-header.tsx: Header de columna con ordenamientomoney-input.tsx: Input especializado para montos monetariospackage-badge.tsx: Badge visual para planes/paquetestime-ago.tsx: Componente de tiempo relativohighlight-text.tsx: Resaltado de texto de búsqueda
Modo Oscuro
El modo oscuro se implementa con next-themes y el hook personalizado use-admin-theme.ts. Todos los componentes de shadcn/ui soportan dark mode de forma nativa a traves de las variables CSS de Tailwind.
Iconos
Se usa Lucide React (lucide-react) como libreria de iconos. Los componentes del itinerario usan un mapa explicito de iconos (itinerary-icons.ts) en vez de barrel imports para optimizar bundle size.
Animaciones
Framer Motion se utiliza para animaciones de transición en:
- Apertura y cierre de modales
- Transiciones entre pasos del wizard
- Animaciones de drag-and-drop
- Microinteracciones de UI
RBAC Dinamico (Permisos por Evento)
Principio rector
El backend es la fuente única de verdad de permisos. El frontend NUNCA hardcodea que puede hacer un rol. Todos los permisos se obtienen dinámicamente desde el endpoint GET /events/:id/access que retorna (hasAccess, role, isSuperAdmin, permissions[]).
Componente ShowIfCan
El componente ShowIfCan verifica permisos del usuario antes de renderizar contenido. Recibe un eventId y una action semantica, y consulta la lista de permisos retornada por el backend para decidir si mostrar u ocultar el contenido.
12 vistas de evento protegidas con ShowIfCan:
| Vista | Acciones protegidas |
|---|---|
| Dashboard del evento | Editar evento, eliminar evento |
| Invitados | Importar, enviar comunicaciones, crear invitado |
| Invitaciones | Crear invitación |
| Mesas | Gestiónar mesas |
| QR Passes | Gestiónar pases QR |
| Ubicaciones | Gestiónar ubicaciones |
| Itinerario | Gestiónar itinerario |
| Música de fondo | Gestiónar música |
| Hospedaje | Gestiónar hospedaje |
| Mesa de regalos | Gestiónar regalos |
| Acceso movil | Gestiónar códigos de acceso |
| Colaboradores | Invitar, cancelar, eliminar colaboradores |
| RSVPs | Gestiónar confirmaciones |
Hook usePermissionsQuery
El hook usePermissionsQuery(eventId) consume GET /events/:id/access y cachea los permisos del usuario para el evento actual. Retorna la lista de permisos como strings ("guests:create:assigned", "events:update:assigned", etc.) que ShowIfCan evalua internamente.
Flujo de verificacion
- El usuario navega a una página de evento
usePermissionsQueryconsultaGET /events/:id/access(cacheado por React Query)- El backend resuelve el rol del usuario via
user_rolesy retornagetRolePermissions(role) ShowIfCanevalua si la acción semantica está incluida en los permisos retornados- Si el permiso existe, renderiza el contenido. Si no, renderiza el
fallback(null por defecto)
Seguridad
Autenticación con Clerk
La autenticación se gestióna via Clerk 6 (@clerk/nextjs):
- Clerk Provider: Contexto de autenticación a nivel de layout raiz
auth()en Server Actions: Cada action obtiene el token JWT de Clerk para llamadas a la API- Protección de rutas: El layout del dashboard verifica autenticación; las rutas de auth (
/sign-in,/sign-up) son públicas
Content Security Policy (CSP)
Headers CSP configurados en next.config.ts con directivas adaptadas a los servicios usados:
| Directiva | Origenes permitidos |
|---|---|
default-src | 'self' |
script-src | 'self', Clerk, Mapbox |
style-src | 'self', 'unsafe-inline' |
img-src | 'self', data:, blob:, Clerk, Mapbox, CDN |
connect-src | 'self', API, Clerk, Mapbox, Sentry |
frame-src | 'self', Clerk |
worker-src | 'self', blob: |
Response Validation en Runtime
Los servicios criticos utilizan apiClient.getValidated() que valida las respuestas del API con schemas Zod en runtime, proporcionando defense-in-depth contra responses inesperadas:
events.ts— Valida estructura de eventosguests.service.ts— Valida arrays de invitadosorganizations.service.ts— Valida contexto de organización
Performance
React Compiler
El proyecto tiene React Compiler habilitado (reactCompiler: true en next.config.ts), lo que proporciona memoizacion automática de componentes y hooks sin necesidad de React.memo, useMemo o useCallback manuales.
Build Optimization
| Optimizacion | Implementación |
|---|---|
| Standalone output | output: 'standalone' para builds optimizados en Docker |
| React Compiler | Memoizacion automática de componentes |
| Import optimization | Mapa explicito de iconos de itinerario (evita barrel import de ~560 iconos) |
| Hook useDebounce | Hook reutilizable para debounce de inputs (evita re-renders innecesarios) |
| React Query | Cache inteligente con invalidación selectiva |
Accesibilidad
Tablas
Las 6 tablas principales implementan atributos ARIA para accesibilidad:
aria-label: Describe el proposito de la tabla (ej: "Lista de invitados")aria-sort: Indica la direccion de ordenamiento en headers conSortableHeader(ascending/descending/none)
Navegación por teclado
Los componentes interactivos tipo card soportan navegación por teclado:
role="button"en cards clickeablesonKeyDownpara Enter y Space- Focus visible en todos los elementos interactivos
ARIA Labels
Los botones de icono (sin texto visible) incluyen aria-label descriptivo para lectores de pantalla.
Testing
Metricas
| Metrica | Cantidad |
|---|---|
| Test suites | 141 |
| Tests individuales | 1,434 |
| Pass rate | 100% |
| Framework | Vitest 4.x + Testing Library |
Cobertura por Area
| Area | Archivos con tests | Tests |
|---|---|---|
| API Services | 33/33 | Tests unitarios |
| React Query Hooks | 39 | Tests unitarios |
| Server Actions | 17/17 | Tests unitarios con Zod validation |
| Validaciones Zod | 16/16 | Tests de schema |
| Componentes | 25+ | Rendering + interacciones |
| Hooks custom | 5+ | Tests con renderHook |
Organización de Tests
Los tests estan co-localizados en directorios __tests__/ junto al código fuente:
src/lib/api/services/__tests__/ # Tests de servicios
src/lib/actions/__tests__/ # Tests de server actions
src/lib/validations/__tests__/ # Tests de schemas Zod
src/lib/hooks/queries/__tests__/ # Tests de React Query hooks
src/components/*/__tests__/ # Tests de componentes
Patron de Tests de Server Actions
// Ejemplo: test de una server action
it('retorna error sin token', async () => {
mockAuth.mockResolvedValueOnce({
getToken: vi.fn().mockResolvedValue(null),
} as any);
const result = await someAction({ eventId: 'evt-1' });
expect(result.success).toBe(false);
});
Arquitectura SOLID
Descomposición de Componentes
Los componentes complejos siguen el principio de responsabilidad única (SRP) mediante extracción de hooks y sub-componentes:
Contract Form Dialog (~400 lineas orquestador):
contracts/hooks/use-contract-calculations.ts— Calculos de precios (useMemo)contracts/hooks/use-contract-form-state.ts— Estado del formulario (useState + useEffect)contracts/contract-event-search.tsx— Búsqueda de eventocontracts/contract-pricing-section.tsx— Sección de precioscontracts/contract-payment-section.tsx— Sección de pago
Groups Manager (~250 lineas orquestador):
groups/hooks/use-groups-manager.ts— Estado y handlersgroups/group-card.tsx— Card individual de grupogroups/group-dialogs.tsx— Dialogs compound (crear, editar, eliminar, asignar)
Scripts de Desarrollo
# Desarrollo local (puerto 5050)
npm run dev
# Cambiar ambiente
npm run env:dev # Desarrollo local
npm run env:dev-remote # API remota desarrollo
npm run env:test-remote # API remota testing
# Build de producción
npm run build
# Desarrollo con Bitwarden Secrets Manager
npm run dev:bws
Referencias
- Código fuente:
nvito-admin/ - API Client:
src/lib/api/client.ts - Response Schemas:
src/lib/api/response-schemas.ts - Services:
src/lib/api/services/ - Server Actions:
src/lib/actions/ - ActionResult:
src/lib/actions/types.ts - Query Hooks:
src/lib/hooks/queries/ - Query Keys:
src/lib/hooks/queries/keys.ts - Validaciones:
src/lib/validations/ - Componentes UI:
src/components/ui/ - CSP Config:
next.config.ts