Docs

Sistema de Invitaciones

Sistema completo de invitaciones en Nvito - maquina de estados, flujos de creación internas y externas, templates, secciones, versionamiento, generación HTML y analiticas.

PublicadoMarzo 2026Equipo de desarrollo, arquitectos, stakeholders

1. Maquina de Estados

Cada invitación en Nvito sigue un ciclo de vida estricto controlado por una maquina de estados. El servicio ApprovalWorkflowService define las transiciones validas y garantiza que ningún cambio de estado ocurra fuera de las reglas establecidas.

1.1 Diagrama de Estados

1.2 Transiciones Detalladas

TransiciónDeAQuien la disparaValidacionesEndpoint
SubmitIN_CONSTRUCTIONUNPUBLISHEDUsuario (owner/admin)Schema no vacio, secciones requeridas presentes (solo internas)POST /invitations/:id/submit-for-approval
ApproveUNPUBLISHEDPUBLISHEDUsuario (owner/admin)Internas: genera HTML y sube a CDN. Externas: verifica que htmlUrl exista. Activa evento si está en DRAFTPOST /invitations/:id/approve
RejectUNPUBLISHEDIN_CONSTRUCTIONUsuario (owner/admin)Requiere razon de rechazoPOST /invitations/:id/reject
Request ChangesUNPUBLISHEDIN_CONSTRUCTIONUsuario (owner/admin)Requiere descripción de cambiosPOST /invitations/:id/reject (action: request_changes)
UnpublishPUBLISHEDUNPUBLISHEDUsuario (owner/admin)Webhook de despublicación enviadoPOST /invitations/:id/unpublish
ClosePUBLISHEDCLOSEDUsuario o sistema (auto-complete/cancel)Registra closedBy y closeReasonPOST /invitations/:id/close
ReopenCLOSEDPUBLISHEDUsuario (owner/admin)Evento debe ser ACTIVE, fecha no debe haber pasadoPOST /invitations/:id/reopen

1.3 Sincronización con Eventos

La maquina de estados de invitaciones está sincronizada con el ciclo de vida de eventos:

  • Invitación publicada (PUBLISHED): Si el evento está en DRAFT, automáticamente transiciona a ACTIVE via EventLifecycleService.activateEventOnPublish().
  • Evento cancelado (CANCELLED): Todas las invitaciones en UNPUBLISHED o PUBLISHED se cierran automáticamente (CLOSED).
  • Evento completado (COMPLETED): Cron job diario (medianoche, timezone America/Mexico_City) cierra invitaciones de eventos cuya fecha ya paso.

2. Tipos de Invitación (InvitationSource)

Nvito soporta dos origenes de invitación, discriminados por el campo source del modelo Invitation:

SourceDescripciónCreaciónEdicionVersiónamiento
INTERNAL (default)Invitación creada con el motor de templates y IA de NvitoTemplate + IA + Editor Visual ArtisanEditor visual, secciones, IA conversacionalSi (max 50 versiones)
EXTERNALHTML disenado fuera de Nvito y cargado manualmenteUpload de archivo HTMLRe-upload completo del HTMLNo (siempre versión 1)

Las invitaciones externas comparten la misma maquina de estados, el mismo sistema de publicación/preview, las mismas analiticas y el mismo renderizado en nvito-invitations. La diferencia radica en como se crea y edita el contenido.


3. Flujo Completo — Invitaciones Internas (INTERNAL)

El recorrido end-to-end de una invitación interna, desde su creación hasta que un invitado la visualiza:

Paso 1: Creación de la Invitación

El usuario crea una invitación asociada a un evento existente. Puede elegir entre:

  • Seleccionar un template: Se obtiene del catálogo via ArtisanTemplateRegistryService.getCatalog(). El template provee colores, fuentes y secciones predeterminadas.
  • Empezar desde cero: Se usa el schema por defecto del tipo de evento (event.eventType.defaultSections).

Restriccion: Máximo 3 invitaciones por evento. Se genera un slug único con formato {eventSlug}-inv-{randomSuffix}.

Endpoint: POST /invitations - Requiere permiso invitations:create:assigned.

Paso 2: Generación con IA (Opcional)

El usuario puede utilizar la generación con IA para crear o modificar el contenido de la invitación a partir de lenguaje natural. El servicio AiGenerationService genera el schema y theme correspondientes, creando una nueva versión en el proceso.

Paso 3: Edicion con Editor Visual Artisan

El usuario edita la invitación en el editor visual de nvito-admin:

  • Modifica secciones, textos, imagenes, colores y fuentes.
  • Reordena secciones con drag & drop.
  • Selecciona variantes visuales para cada sección.
  • Previsualiza en tiempo real via POST /invitations/:id/render-draft (compilacion sin persistencia a CDN).

Endpoint: PATCH /invitations/:id - Actualiza schema y/o theme.

Paso 4: Secciones y Variantes

Cada sección puede configurarse con una variante visual diferente. El usuario selecciona variantes desde el catálogo de section-variants/ y la configuración se guarda en schema.sectionConfig[sectionType].variantId.

Paso 5: Preview

El usuario genera un token temporal de preview (JWT, 15 minutos de validez) via POST /invitations/:id/preview-token. Este token permite visualizar la invitación compilada sin necesidad de publicarla, accesible via GET /invitations/preview/:token.

Paso 6: Publicación

Al aprobar la invitación (POST /invitations/:id/approve):

  1. Se compila el schema a HTML/CSS/JS via TemplateCompilerService.
  2. Se optimiza y agregan meta tags SEO via HtmlOptimizerService.
  3. Se sube a CDN (R2/MinIO) via StorageService.
  4. Se actualizan htmlUrl, cssUrl, jsUrl en la BD.
  5. Se envia webhook de revalidación a nvito-invitations para ISR.
  6. Si el evento está en DRAFT, se activa automáticamente.

Paso 7: Visualización por Invitados

El invitado recibe un enlace con formato https://nvito.mx/i/{slug} o, si se habilita la personalización, https://nvito.mx/i/{slug}?t={shortCode}. La aplicación nvito-invitations (Next.js) renderiza el micrositio usando ISR (Incremental Static Regeneration):

  1. Consulta GET /invitations/public/:slug para obtener las URLs del HTML.
  2. Renderiza el micrositio completo con las secciones configuradas.
  3. Registra eventos de analiticas (VIEW) en la API.
  4. Si la URL incluye ?t={shortCode}, resuelve los datos del invitado via GET /invitations/public/guest/{shortCode} y personaliza el loader (pantalla de carga) y la sección RSVP con el nombre del invitado.

La personalización es 100% client-side: el HTML estático es identico para todos los invitados, y la resolución del nombre ocurre en JavaScript al cargar la página. Si el shortCode no existe o es invalido, se muestran textos de fallback generico configurables. Ver detalles en Motor de Invitaciones — Personalización por Invitado.

Paso 8: RSVP del Invitado

El invitado interactua con la sección RSVP integrada en el micrositio. Si la invitación fue abierta con un shortCode valido, la sección RSVP muestra un saludo personalizado con el nombre y título del invitado (ej: "Ing. Juan, confirma tu asistencia"). Al confirmar o declinar, se registra el RSVP en la API y se dispara un evento de analitica RSVP_SUBMIT.


4. Flujo Completo — Invitaciones Externas (EXTERNAL)

Las invitaciones externas permiten a usuarios con diseños HTML propios (ej. diseñadores, agencias) publicar sus invitaciones a traves de la plataforma Nvito, aprovechando el hosting, las analiticas, el sistema RSVP y la distribución via comunicaciones.

4.1 Diagrama de Secuencia

4.2 Creación

El usuario sube un archivo HTML (.html / .htm) desde nvito-admin. El archivo se envia como string al endpoint POST /invitations/external.

Proceso de creación:

  1. Se verifica que el evento exista y pertenezca a la organización del usuario.
  2. Se verifica el limite de 3 invitaciones por evento.
  3. Se valida el HTML con HtmlValidatorService (ver sección 4.4).
  4. Se genera un slug temporal con formato {eventSlug}-ext-{random6chars}.
  5. Se crea el registro en BD con source: 'EXTERNAL', status: 'IN_CONSTRUCTION', schema: {}, theme: {}.
  6. Se sube el HTML a CDN (R2/MinIO) via StorageService.
  7. Se guarda un backup del HTML raw en el campo externalHtmlRaw de la BD.
  8. Se retorna la invitación creada junto con warnings (si los hay).

Campos opcionales: seoTitle (max 200 chars) y seoDescription (max 500 chars) para personalizar los meta tags SEO.

4.3 Restricciones

Las invitaciones externas tienen las siguientes restricciones respecto a las internas:

FuncionalidadInternaExterna
Editor Visual ArtisanSiNo
Generación/modificación con IASiNo
Secciones editablesSiNo
Versiónamiento (rollback)Si (max 50)No
Edicion de schema/themeSi (PATCH /invitations/:id)No (lanza error)
Re-upload de HTMLNoSi (solo en IN_CONSTRUCTION)
Descarga del HTML originalNoSi
Preview con tokenSiSi
PublicaciónSi (genera HTML)Si (usa HTML existente)
AnaliticasSiSi
RSVPSiSi (si el HTML incluye formulario RSVP)
ComunicacionesSiSi

4.4 Validación de HTML (HtmlValidatorService)

El servicio HtmlValidatorService ejecuta un pipeline de 9 pasos de validación que retorna { valid: boolean, errors: string[], warnings: string[] }:

PasoTipoDescripción
1. TamanoErrorBloquea si el HTML supera 5 MB
2. EstructuraErrorRequiere <!DOCTYPE html> o <html> + etiqueta <body>
3. Scripts peligrososErrorBloquea: eval(), new Function(), document.cookie, document.domain, window.location =, XMLHttpRequest, WebSocket(), importScripts(), navigator.sendBeacon, fetch()
4. IframesErrorSolo permitidos desde dominios de whitelist (Google, YouTube, Vimeo, Spotify, Maps)
5. Formularios externosError<form action="http(s)://..."> con URL absoluta externa bloqueado
6. Meta refreshError<meta http-equiv="refresh"> bloqueado
7. Event handlersErroronerror/onload con eval, fetch, document.cookie, window.location bloqueado
8. URLs relativasWarningAdvierte sobre src/href con paths relativos (que no funcionaran)
9. ViewportWarningAdvierte si falta <meta name="viewport">

Los errores bloquean la creación y retornan 400. Los warnings no bloquean pero se informan al usuario.

4.5 Re-upload

El usuario puede reemplazar el HTML de una invitación externa que este en estado IN_CONSTRUCTION. El HTML anterior se sobreescribe de forma irreversible.

Restricciones del re-upload:

  • Solo invitaciones con source: 'EXTERNAL'
  • Solo en estado IN_CONSTRUCTION (si está publicada, debe despublicarse primero)
  • El nuevo HTML pasa por la misma validación de 9 pasos

Endpoint: PUT /invitations/:id/external/reupload

4.6 Descarga del HTML Original

El usuario puede descargar el HTML original que subio, almacenado en el campo externalHtmlRaw de la BD (no requiere acceso a S3).

Endpoint: GET /invitations/:id/external/download

4.7 Publicación de Externas

La publicación de invitaciones externas usa el mismo flujo de aprobacion (POST /invitations/:id/approve), pero con diferencias:

  1. No se genera HTML: el HTML ya existe en CDN (subido en el paso de creación/re-upload).
  2. No se validan secciones: solo se verifica que htmlUrl no sea nulo.
  3. Se genera slug amigable: si el evento tiene un custom domain activo y verificado, el slug se actualiza con el path del custom domain (ej. boda-real-2026). Si no, se usa el nombre del evento slugificado.
  4. Se activa el evento si está en DRAFT (igual que internas).
  5. Se envia webhook ISR a nvito-invitations para revalidar la página.

4.8 Endpoints de Invitaciones Externas

MetodoEndpointDescripciónPermiso
POST/invitations/externalSubir HTML y crear invitación externainvitations:create:assigned
PUT/invitations/:id/external/reuploadReemplazar HTML de invitación externainvitations:update
GET/invitations/:id/external/downloadDescargar HTML originalAutenticado

5. Templates

5.1 Catálogo de Templates

Los templates son plantillas prediseadas almacenadas en la base de datos (tabla templates) con estado ACTIVE. Al iniciar el módulo, ArtisanTemplateRegistryService carga todos los templates activos en un cache en memoria.

Cada template está asociado a un tipo de evento y provee una configuración completa:

CampoDescripción
slugIdentificador único (e.g., wedding-classic, xv-princess)
eventTypeTipo de evento asociado (e.g., wedding, graduation, birthday)
configObjeto JSON con colores, fuentes, secciones y orden
previewImageUrlURL de imagen de preview para el catálogo
tagsEtiquetas para filtrado y búsqueda

5.2 Templates por Defecto por Tipo de Evento

Tipo de EventoTemplate por Defecto
Weddingwedding-classic
XV Añosxv-princess
Birthdaybirthday-festive
Baptismbaptism-celestial
Communioncommunion-serene
Baby Showerbabyshower-sweet
Graduationgraduation-classic
Corporatecorporate-clean
Otherother-versatile

5.3 Estructura del Template (TemplateMeta)

Cada template expone: id (slug), eventType, name, description, previewImageUrl, tags, defaultColors (primary, secondary, accent, background, text), defaultFonts (heading, body), defaultSections (visibles por defecto) y optionalSections (ocultas por defecto).

5.4 Compilacion de Templates

El TemplateBuilderService construye el HTML a partir de la config: lee secciones, selecciona variantes del registro section-variants/, ensambla HTML con placeholders Handlebars ({{variable}}), y cachea en ArtisanTemplateRegistryService.

Endpoints públicos: GET /invitations/templates (catálogo filtrable por eventType), GET /invitations/templates/:templateId (metadata), GET /invitations/variants (variantes por tipo).


6. Secciones

6.1 Tipos de Sección Disponibles

El sistema define 18 tipos de sección con 121 variantes visuales en total:

hero (13 variantes), welcome (9), event_details (7), countdown (8), story (8), gallery (11), itinerary (5), location (6), rsvp (8), registry (5), accommodation (5), faq (5), music (5), sponsors (5), prayer (5), thank_you (5), dress_code (5), footer (6).

6.2 Estructura de una Variante de Sección

interface SectionVariant {
  id: string;          // e.g., "hero-centered-overlay"
  sectionType: string; // e.g., "hero"
  name: string;        // Nombre legible en español
  description: string; // Descripción corta
  css: string;         // CSS específico de la variante
  html: string;        // HTML con placeholders {{}} Handlebars
}

6.3 Ordenamiento y Visibilidad

  • Orden: El schema almacena un array sectionOrder que define el orden de renderizado. El usuario puede reordenar secciones con drag & drop en el editor visual.
  • Visibilidad: El objeto sectionVisibility del schema controla que secciones se muestran (true) o se ocultan (false).
  • Configuración por sección: sectionConfig[sectionType] almacena configuración específica, incluyendo variantId para override de la variante visual.

6.4 Resolución de Variantes

Cuando se compila una invitación, el HtmlGeneratorService resuelve las variantes para cada sección visible:

  1. Si el usuario selecciono un variantId en sectionConfig, se usa esa variante.
  2. Si la sección existe en el template pero el usuario cambio la variante, se reemplaza el bloque HTML/CSS.
  3. Si la sección no existe en el template, se inyecta la variante seleccionada (o la primera disponible).

7. Versiónamiento

7.1 Modelo InvitationVersión

Cada cambio significativo en una invitación genera una versión (snapshot). El servicio VersiónManagerService gestióna el ciclo de vida de las versiones. Campos principales: id (UUID), invitationId, versiónNumber (secuencial), schema (JSON), theme (JSON), changeType (enum: ai_generation | ai_modify | manual_edit | rollback), changePrompt, createdBy, createdAt, htmlUrl, cssUrl, jsUrl.

7.2 Creación de Versiónes

Las versiones se crean en los siguientes escenarios:

  • Edicion manual (manual_edit): Cuando el usuario guarda cambios desde el editor visual.
  • Generación con IA (ai_generation): Cuando se genera contenido nuevo con IA.
  • Modificación con IA (ai_modify): Cuando se modifica contenido existente con IA.
  • Rollback (rollback): Cuando se restaura una versión anterior.

Limite: Máximo 50 versiones por invitación. Al exceder el limite, se elimina la versión más antigua automáticamente.

7.3 Rollback a Versión Anterior

El proceso de rollback (POST /invitations/:id/versións/:versiónNumber/rollback):

  1. Se obtiene la versión objetivo por número.
  2. Se actualiza el schema y theme de la invitación con los datos de la versión objetivo.
  3. Se crea una nueva versión de tipo rollback que registra la restauración.
  4. Se retorna la invitación actualizada y la nueva versión creada.

7.4 Comparacion y Endpoints

El endpoint GET /invitations/:id/versións/compare?versión1=X&versión2=Y compara dos versiones aplanando claves JSON y reportando campos agregados, eliminados y modificados.

Endpoints: POST /versións (crear), GET /versións (historial), GET /versións/stats, GET /versións/:n (específica), POST /versións/:n/rollback, GET /versións/compare.


8. Generación HTML

8.1 Pipeline de Compilacion

El proceso de compilacion transforma el schema JSON de una invitación en HTML/CSS/JS optimizado y listo para producción. Los servicios involucrados son:

ServicioResponsabilidad
HtmlGeneratorServiceOrquestador principal del pipeline
ArtisanTemplateRegistryServiceProvee el HTML base del template
TemplateCompilerServiceCompila placeholders Handlebars con datos reales
HtmlOptimizerServiceOptimiza bundle y agrega meta tags SEO
StorageServiceSube archivos a CDN (R2/MinIO)
WebhookServiceNotifica a nvito-invitations para revalidación ISR

8.2 Proceso de Compilacion Paso a Paso

  1. Obtener schema y theme de la invitación (o de una versión específica).
  2. Resolver template: Obtener el HTML base del registro via templateId del schema.
  3. Construir TemplateData: Combinar datos del schema con colores, fuentes y metadatos.
  4. Calcular secciones visibles: Aplicar sectionOrder y sectionVisibility.
  5. Resolver variantes: Reemplazar bloques de sección con variantes seleccionadas por el usuario.
  6. Compilar Handlebars: Reemplazar todos los placeholders {{variable}} con datos reales.
  7. Optimizar: Minificar, agregar meta tags SEO, Open Graph, etc.
  8. Subir a CDN: Persistir index.html, styles.css y scripts.js en R2/MinIO.
  9. Actualizar BD: Guardar htmlUrl, cssUrl, jsUrl y htmlGeneratedAt en la invitación.
  10. Webhook ISR: Notificar a nvito-invitations para revalidar la página.

8.3 Modos de Compilacion

ModoEndpointPersistenciaUso
DraftPOST /invitations/:id/render-draftNo (solo respuesta)Editor visual, preview en tiempo real via iframe
GeneratePOST /invitations/:id/generate-htmlSi (CDN + BD)Generación independiente sin cambiar estado
PublishPOST /invitations/:id/publish-htmlSi (CDN + BD + estado)Generación + publicación en un solo paso

8.4 Diagrama de Publicación (Internas)

8.5 Rutas de Almacenamiento en CDN

Los archivos se almacenan con la siguiente estructura en R2/MinIO:

/{organizationId}/{eventId}/{invitationId}/v{versión}/index.html
/{organizationId}/{eventId}/{invitationId}/v{versión}/styles.css
/{organizationId}/{eventId}/{invitationId}/v{versión}/scripts.js

8.6 Custom Domains

El WebhookService soporta dominios personalizados. Al publicar, busca CustomDomain activo y verificado del evento, construye paths de revalidación (/i/{slug} + /d/{customDomain}), y envia webhook con header X-Revalidate-Secret.


9. Analiticas

9.1 Modelo de Eventos

El sistema de analiticas rastrea la interacción de los invitados con las invitaciones publicadas. Los eventos se almacenan en InvitationAnalyticsEvent:

Tipo de EventoDescripción
VIEWEl invitado visualizo la invitación
RSVP_OPENEl invitado abrio el formulario de RSVP
RSVP_SUBMITEl invitado envio su respuesta RSVP
LINK_CLICKEl invitado hizo clic en un enlace (mesa de regalos, ubicación, etc.)
SHAREEl invitado compartio la invitación
DOWNLOADEl invitado descargo contenido de la invitación

9.2 Datos Capturados por Evento

Cada evento registra: invitationId, eventType, visitorId (anonimo via cookie/fingerprint), ipAddress, userAgent, country (ISO 2 letras), city, device (mobile/desktop/tablet), browser, os, referrer y metadata (JSON adicional).

9.3 Resumen Agregado (InvitationAnalyticsSummary)

Para cada invitación se mantiene un resumen agregado que se actualiza en tiempo real (fire-and-forget) después de cada evento:

MetricaDescripción
totalViewsTotal de visualizaciones
uniqueVisitorsVisitantes únicos (por visitorId distinto)
rsvpOpensVeces que se abrio el formulario RSVP
rsvpSubmitsRespuestas RSVP enviadas
linkClicksClics en enlaces
sharesVeces compartida
downloadsDescargas realizadas
conversiónRateTasa de conversión: (rsvpSubmits / totalViews) * 100
deviceBreakdownDistribucion por tipo de dispositivo (JSON)
countryBreakdownDistribucion por pais (JSON)
topReferrersTop 10 origenes de trafico (JSON)

9.4 Flujo de Tracking

  1. nvito-invitations (frontend público) detecta la interacción del invitado.
  2. Envia un POST /events/:eventId/analytics/track con los datos del evento (endpoint público, sin autenticación).
  3. nvito-api crea el registro en InvitationAnalyticsEvent.
  4. Asincrona y de forma no bloqueante (fire-and-forget), actualiza el InvitationAnalyticsSummary:
    • Incrementa el contador correspondiente.
    • Recalcula visitantes únicos si es un evento VIEW.
    • Recalcula metricas agregadas (conversión rate, desglose por dispositivo, pais y referrers).

9.5 Endpoints de Analiticas

EndpointMetodoAutenticación
/events/:eventId/analytics/trackPOSTPublico
/events/:eventId/analytics/overviewGETowner/admin
/events/:eventId/analytics/guestsGETowner/admin
/events/:eventId/analytics/rsvpGETowner/admin
/events/:eventId/analytics/timelineGETowner/admin
/events/:eventId/analytics/invitations/:id/summaryGETowner/admin
/events/:eventId/analytics/invitations/:id/detailedGETowner/admin

9.6 Analiticas a Nivel de Evento

El AnalyticsService provee analiticas agregadas a nivel de evento combinando datos de todas las invitaciones: overview (invitados, RSVP, mesas, QR), guest analytics (por grupo y estado RSVP), RSVP timeline (ultimos 30 dias), y actividad reciente (audit logs con fallback a consulta directa).

Referencias

Archivos fuente principales: invitations.service.ts, invitations.controller.ts, external-invitation.controller.ts, external-invitation.service.ts, html-validator.service.ts, approval-workflow.service.ts, versión-manager.service.ts, html-generator.service.ts, template-compiler.service.ts, artisan-template-registry.service.ts, html-optimizer.service.ts, webhook.service.ts, analytics.service.ts, section-variants/index.ts. Documentación relacionada: CICLO_VIDA_EVENTOS_INVITACIONES.md.

Esta pagina fue util?