Arquitectura Técnica y Diseño de Sistemas
Documentación sistémica unificada de la plataforma FaCyT Events
INFORME TÉCNICO Y DOCUMENTACIÓN SISTÉMICA DE LA PLATAFORMA "FACYT EVENTS"
- Asignatura: Sistemas de Información
- Institución: Facultad Experimental de Ciencias y Tecnología (FaCyT) — Universidad de Carabobo
- Autor y Desarrollador: Guillermo Cedeño
- Estado de Despliegue: 100% Operativo en Producción (https://facyt-events.sunlessteam.com)
- Fecha: Julio 2026
CAPÍTULO I: ENFOQUE SISTÉMICO Y DELIMITACIÓN DE FRONTERAS
1.1 Identificación y Delimitación del Problema Organizacional
Simulación: La Facultad Experimental de Ciencias y Tecnología (FaCyT) alberga múltiples departamentos docentes y de investigación cuyas dinámicas de extensión y divulgación científica generan semanalmente conferencias, talleres, seminarios y jornadas de pasantías. No obstante, la facultad presentaba una marcada debilidad en la gestión integrada de estas actividades, operando bajo un esquema manual y descentralizado caracterizado por los siguientes factores de ineficiencia:
- Inexistencia de un Canal Único de Difusión: La cartelera física centralizada y el uso informal de redes sociales personales provocaban altos niveles de desinformación estudiantil y docente, lo que impactaba directamente en bajas tasas de asistencia y participación.
- Conflictos Críticos de Espacios Físicos: La reserva de áreas clave (tales como el Auditorio "Benito Pereda", salones comunes y laboratorios de computación) se procesaba verbalmente o por correos no unificados, propiciando colisiones físicas de horarios y forzando reprogramaciones de última hora que afectaban la logística de la facultad.
- Ausencia de Trazabilidad e Historial: No se disponía de un registro fidedigno acerca del ciclo de vida de los eventos, ni de herramientas para medir la asistencia de forma ágil, obligando a usar listados de papel que impedían la emisión rápida de acreditaciones de participación.
FaCyT Events surge como una solución tecnológica integrada que formaliza el ciclo completo de los eventos académicos, automatizando las restricciones físicas de espacio y tiempo, y dotando a la facultad de canales interactivos de difusión y control de asistencia in situ.
1.2 Alcance y Fronteras del Sistema
El sistema ha sido estrictamente delimitado para equilibrar un núcleo de alto valor funcional con una complejidad arquitectónica escalable:
- Inclusiones (Dentro del alcance implementado):
date_change_requests).pdfkit).cid) para garantizar su correcta visualización en clientes móviles como Gmail.Recharts) y métricas de uso de infraestructura./documentacion) que renderiza de manera elegante los entregables académicos del proyecto directamente desde el menú principal.- Exclusiones (Fuera del alcance / Escalabilidad futura):
is_paid y price), pero la pasarela de pagos no forma parte de esta versión de producción estable.1.3 Matriz de Roles y Actores de la Organización
La plataforma interactúa de manera diferenciada con cinco actores, garantizando un Control de Acceso Basado en Roles (RBAC) robusto a nivel de backend:
| Actor | Permisos Operativos | Límites de Seguridad en el Sistema |
|---|---|---|
| Público General | Consulta la cartelera en modo lista o calendario; se inscribe de forma anónima en eventos abiertos; navega y consulta el módulo de Documentación Interactiva. | No posee acceso a ningún dashboard ni credenciales de sesión. |
| Usuario Registrado | Consulta eventos, gestiona su perfil (con validación de contraseñas duplicadas), visualiza su historial de asistencias y sus códigos QR personales. | Acceso restringido exclusivamente a su propia consola académica y ajustes personales. |
| Organizador (Docente / Dependencia) | Postula eventos, carga imágenes locales a través de Multer, invita y asigna moderadores, gestiona reprogramaciones de eventos rechazados. | No puede aprobar, rechazar o cancelar eventos. Su catálogo de moderadores a invitar se restringe a los creados por él mismo (createdById). |
| Moderador | Acepta/rechaza invitaciones de moderación; gestiona el check-in QR móvil continuo e introduce asistencias manuales por Cédula de Identidad. | Únicamente puede interactuar con eventos donde haya aceptado formalmente la invitación de moderación. |
| Coordinador (Decanato / Extensión) | Posee el control absoluto. Revisa, aprueba, rechaza (justificando motivo) y cancela eventos; gestiona espacios físicos (Crear/Editar); administra reportes. | Administrador global. Tiene la facultad de alterar la disponibilidad operativa de las áreas físicas y anular administrativamente colisiones. |
1.4 Mapeo de Componentes Sistémicos del Proceso
[Entradas] ──────────────────► [Procesos de Negocio] ──────────────────► [Salidas del Sistema]
- Datos Solicitud (Evento) - Motor de Validación Horaria - Cartelera Dinámica (Filtros)
- Credenciales de Acceso - Transición de Estados - Diplomas PDF (Nodemailer)
- Registro de Asistente - Cooldown del Lector QR - QRs Inline Embebidos (cid)
- Escaneo de QR / Cédula - Solicitudes de Cambio de Fecha - Métricas y Gráficos UI- Propósito: Optimizar los canales de difusión de las actividades académicas de la FaCyT, garantizando una planificación espacial libre de colisiones y proveyendo un mecanismo de check-in automatizado e instantáneo.
- Entradas: Formularios de postulación (títulos, tipo de actividad, aforo, fechas, horas y espacio), registros de usuario, datos personales de inscripción, cargas físicas locales de imágenes y lecturas de tokens QR desde smartphones.
- Procesos: Validación automática de solapamiento temporal por espacio, flujo de control transicional de estados, cifrado de contraseñas con bcrypt (12 rondas de sal), procesamiento asíncrono de asistencia con bus de eventos, control de lectura repetida por cooldown, y renderizado en memoria de diplomas.
- Salidas: Cartelera pública digital reactiva con transiciones fluidas de opacidad, reportes de asistencia en el historial, certificados en formato PDF enviados con adjuntos de QR incrustados inline (
cid:qrcode_image), alertas de colisión horaria y gráficos de tasa de uso en el panel de control. - Retroalimentación: Notificaciones de error en tiempo real al solapar reservas, avisos preventivos de aforo sobrepasado, envío automático de correos con justificación de rechazo, solicitudes de reprogramación por organizadores y el buzón de incidencias técnicas.
- Recursos del Sistema: Para garantizar la operatividad continua, la integridad transaccional y la disponibilidad de la plataforma FaCyT Events en el entorno real de la facultad, el sistema de información consume y articula tres categorías de recursos core:
- Restricciones: Aforo máximo del espacio físico solicitado, límites del horario de operación de la FaCyT, e imposibilidad de que el sistema apruebe de manera automática un evento sin una confirmación manual del Coordinador (salvaguarda de control institucional).
CAPÍTULO II: MODELADO DINÁMICO DE PROCESOS (WORKFLOWS)
2.1 Fase A: Postulación e Inicialización del Evento
- El Organizador ingresa a su módulo en Next.js (
/dashboard/nuevo-evento). - Rellena el formulario interactivo: título del evento, descripción detallada, tipo de actividad (charla, taller, jornada), aforo estimado y la selección del espacio físico deseado.
- El Programador Horario Avanzado: El usuario interactúa con una grilla gráfica de bloques de 30 minutos. Al seleccionar la hora de inicio y fin, el frontend calcula la duración total en minutos.
- Validación Preventiva de Aforo vs. Capacidad: El frontend cruza la capacidad del espacio seleccionado con el aforo estimado. Si el aforo supera la capacidad del espacio, el sistema utiliza el hook
useConfirmpara alertar al usuario a través de una modal interactiva suave de advertencia, permitiendo continuar o modificar los datos antes de procesar el submit. - Carga Local de Imágenes (Multer): El organizador arrastra la imagen de portada y galerías. El backend de NestJS procesa la subida mediante almacenamiento local físico en el directorio estático
/uploads/. - El evento se envía con estado inicial de
solicitado.
2.2 Fase B: Delegación y Asignación de Moderadores
- Un evento en estado
solicitadorequiere moderadores para el control de acceso en puerta. - El Organizador busca moderadores en un catálogo integrado. Para mantener la privacidad departamental, el organizador solo visualizará a los moderadores creados y delegados bajo su propia cuenta (
createdById). - Al seleccionar un moderador, se inserta una solicitud con estado
pendienteen la tabla asociativamoderator_requests. - El moderador inicia sesión, visualiza en su panel las invitaciones pendientes y presiona Aceptar o Rechazar. Si acepta, la solicitud muta a
aceptadoy el evento se indexa en su dashboard de escaneo.
2.3 Fase C: Revisión, Validación de Colisiones y Aprobación
- El Coordinador accede a su bandeja de solicitudes pendientes (
/dashboard/admin-eventos). - Al abrir la ficha, el backend de NestJS ejecuta la Regla de Oro de Validación Horaria consultando directamente a la base de datos PostgreSQL:
- Comportamiento en UI ante Colisiones: Si se detecta solapamiento, el backend retorna una alerta de conflicto. La interfaz de Next.js deshabilita instantáneamente el botón "Aprobar" y despliega un banner de advertencia en color rojo detallando el nombre y horario del evento colisionado. El coordinador debe rechazar el evento (indicando un motivo de reprogramación) o esperar un ajuste del organizador.
- Si el espacio está libre, el evento transiciona a
en_revisiony posteriormente el Coordinador presiona Aprobar. El estado cambia aaprobadoy el evento se indexa de inmediato en la Cartelera Pública. - El Coordinador posee acceso a
/dashboard/espaciosdonde puede dar de alta o actualizar las características de las aulas o auditorio a través de un formulario dinámico dual-mode.
space_id que presente intersección temporal:2.4 Fase D: Difusión, Consulta Pública e Inscripción Híbrida
- Cualquier miembro del Público General accede a la cartelera utilizando la barra de búsqueda en tiempo real, filtros dinámicos por salones o el Modo Calendario mensual.
- Al seleccionar "Inscribirse", Next.js valida el estado de autenticación:
- El backend de NestJS genera un token QR de alta entropía (
qrToken), lo almacena, y despacha un correo de confirmación. - Solución a QRs Rotos en el Cliente de Correo: Para mitigar el error en Gmail de no renderizar imágenes codificadas en base64 crudo o bloquear links externos, el backend genera el código QR como un buffer PNG y lo inyecta como un recurso embebido inline mediante el estándar Content-ID (
cid:qrcode_image), asegurando que la imagen se renderice de forma segura e impecable dentro del correo.
userId = null).2.5 Fase E: Control de Acceso y Ejecución in situ (Check-in QR)
- El día de la actividad académica, el Moderador asignado abre el lector en su smartphone.
- Escáner QR Continuo con Cooldown de Lectura: Al detectar el QR, la transmisión de la cámara de Next.js no se detiene ni se desmonta. En su lugar, el escáner se pausa temporalmente (ignorando nuevos frames) y despliega una modal flotante con los datos del participante (Nombre, Cédula, Correo y estado de asistencia). El moderador revisa y presiona "Confirmar Entrada".
- Mecanismo de Prevención de Bucle: Tras confirmar o cerrar la modal, el sistema activa una ventana de cooldown mínimo de 4 segundos utilizando referencias de React (
useRef). Esto evita que, si el moderador mantiene la cámara apuntando involuntariamente al mismo código físico, el lector registre consecutivamente una doble lectura. - El backend cambia el estado de asistencia (
attended) a presente y guarda la hora exacta del ingreso (scannedAt). - Mecanismo de Contingencia Manual: Si el participante no posee el código físico o electrónico, el moderador utiliza la barra de búsqueda integrada en su panel, ingresa la Cédula del alumno, valida su identidad y presiona Marcar Asistencia manualmente.
2.6 Fase F: Cierre del Evento, Certificación Automatizada e Historial
- Al concluir de forma exitosa todas las ponencias, el Coordinador cambia el estado del evento de
aprobadoarealizado. - Trigger de Certificación Dinámica (Gating de Seguridad): NestJS intercepta este cambio de estado y compila dinámicamente un documento PDF en memoria utilizando la librería
pdfkit(sin almacenar archivos temporales en el disco de la VPS). El certificado incluye un código alfanumérico único impreso para su posterior verificación. - El servicio de correo inyecta este buffer binario en un canal SMTP y despacha de manera automatizada el diploma en PDF directamente al correo de cada participante que posea el estado de
attended = true. - El evento se desplaza formalmente al Historial de Eventos de la facultad en la ruta
/dashboard/historial, una sección dotada de filtros avanzados que exhibe estadísticas reales de asistencia y galerías de fotos de evidencia.
2.7 Flujos de Excepción, Cancelaciones y Reprogramaciones por Ajuste de Fecha
- Flujo Alternativo: Cancelación de Eventos: Si por motivos de fuerza mayor la actividad no se ejecuta, el Coordinador o el Organizador pueden cambiar el estado a
cancelado. Al atravesar esta transición, el sistema libera inmediatamente el espacio físico en la base de datos para futuras reservas, marca las inscripciones como canceladas y envía un correo automatizado a todos los participantes inscritos notificando la suspensión de la actividad académica. - Flujo Alternativo: Rechazo y Solicitud de Ajuste de Fecha:
rejectionReason).date_change_requests en estado pendiente./dashboard/solicitudes). La vista le presenta una comparativa clara en formato de tarjetas ("Fecha Original" vs. "Fecha Propuesta"), y realiza la validación horaria en tiempo real.startTime, endTime y spaceId, transicionando el evento a estado solicitado (o aprobado según decida el administrador) y notificando al docente. Si la rechaza, el registro transiciona a rechazado especificando el motivo de rechazo.2.8 Subsistema de Envío de Mails Informativos (Retroalimentación Activa del Sistema)
#### A. Flujo de Control Desacoplado Asíncronamente (Event-Driven)
Para evitar el bloqueo de hilos por la latencia de la API de mensajería, las operaciones de envío se encuentran totalmente desacopladas de las transacciones principales de PostgreSQL utilizando el bus de eventos asíncrono @nestjs/event-emitter:
[Módulo Operativo Backend]
│ (Inscripción de Usuario, Reprogramación o Cambio de Estado)
▼
this.eventEmitter.emit('evento.trigger', data) (Respuesta HTTP inmediata)
│
▼ [Bus de Eventos Asíncronos]
│
@OnEvent('evento.trigger')
Handler del MailsService (Procesado en segundo plano)
│
├─► Compila plantilla con Handlebars
├─► Genera Buffers / QR Inline / Certificados PDF en memoria
└─► Despacha vía SMTP de producciónEste patrón no bloqueante libera de inmediato la petición del navegador en milisegundos, retornando respuestas fluidas e instantáneas en el frontend.
#### B. Triggers del Ciclo de Notificaciones
La plataforma posee parametrizada la retroalimentación automática para los siguientes escenarios operativos críticos de la FaCyT:
| Evento Organizacional | Disparador (Trigger) en Backend | Destinatario | Acción de Comunicación Realizada |
|---|---|---|---|
| Creación de Postulación | event.created | Coordinador | Notifica la existencia de un nuevo evento en estado solicitado pendiente de revisión y aforo. |
| Aprobación de Solicitud | event.approved | Organizador | Envía confirmación formal detallando espacio y fecha, transicionando a cartelera pública. |
| Rechazo de Solicitud | event.rejected | Organizador | Envía la devolución del evento a borrador, adjuntando la razón formal escrita por el Coordinador. |
| Inscripción Confirmada | registration.completed | Participante | Despacha la confirmación de cupo con su código QR personalizado incrustado como adjunto seguro inline (cid:qrcode_image). |
| Reprogramación de Evento | event.rescheduled | Inscritos | Envía aviso urgente en copia oculta (BCC) alertando sobre la modificación del espacio u hora de ejecución. |
| Cancelación de Actividad | event.cancelled | Inscritos | Alerta en copia oculta (BCC) sobre la suspensión definitiva por fuerza mayor, liberando la disponibilidad del salón. |
| Emisión de Certificado | event.completed | Asistentes | Compila y envía de forma inmediata el diploma de participación digital con código de verificación impreso. |
2.9 Diagrama de Flujo Secuencial del Sistema (Workflow Unificado)
CAPÍTULO III: ARQUITECTURA LÓGICA DE DATOS
3.1 Justificación Teórica de la Selección de PostgreSQL
La elección de PostgreSQL como motor de base de datos relacional para la FaCyT se fundamenta bajo sólidos criterios de diseño e ingeniería de software:
- Integridad Referencial Absoluta: La consistencia operativa exige que los certificados emitidos estén estrechamente vinculados a inscripciones legítimas y estas, a su vez, a eventos de salones existentes. PostgreSQL garantiza esta consistencia mediante restricciones de clave externa (
Foreign Keys) con cascadas de borrado seguras (onDelete: 'cascade'). - Soporte para Transacciones ACID: El algoritmo de prevención de colisiones horarias requiere un aislamiento transaccional estricto. Si dos organizadores intentan reservar el auditorio en el mismo microsegundo, PostgreSQL asegura mediante bloqueos atómicos que solo una de las transacciones sea exitosa, impidiendo solapamientos duplicados.
- Tipado Estricto y Extensiones: Permite el uso nativo de tipos de datos indispensables para la robustez del sistema: UUIDs para llaves primarias no predecibles, enums nativos para el control cerrado de estados y marcas temporales complejas con zona horaria (
timestamp with time zone) para neutralizar desfases de servidores VPS.
3.2 Diagrama Entidad-Relación (ERD Lógico y Normalizado)
+---------------------------------+ +---------------------------------+
| users | | spaces |
+---------------------------------+ +---------------------------------+
| PK id: uuid | | PK id: uuid |
| full_name: varchar(255) | | name: varchar(255) |
| email: varchar(255) [UNIQUE] | | type: enum(auditorio,...) |
| password_hash: varchar(255) | | capacity: integer |
| role: enum(coordinador,...) | | location: varchar(255) |
| cedula: varchar(20) [UNIQUE] | | is_active: boolean |
| is_active: boolean | | directions: text |
| created_at: timestamp | +---------------------------------+
+---------------------------------+ │
│ │ 1:N space_id
│ 1:N organizer_id ▼
│ +---------------------------------+
└──────────────────────►| events |
+---------------------------------+
| PK id: uuid |
| FK space_id: uuid |
| FK organizer_id: uuid |
| title: varchar(500) |
| description: text |
| type: varchar(100) |
| status: enum(solicitado,...) |
| start_time: timestamp (tz) |
| end_time: timestamp (tz) |
| estimated_attendance: int |
| rejection_reason: text |
+---------------------------------+
│ │ │
1:N event_id (inv) │ │ │ 1:N event_id
┌────────────────────────────────┘ │ └──────────────────────┐
▼ ▼ ▼
+---------------------------------+ +---------------------------------+ +---------------------------------+
| moderator_requests | | registrations | | date_change_requests |
| +-----------------------------+ | +---------------------------------+ +---------------------------------+
| | PK id: uuid | | | PK id: uuid | | PK id: uuid |
| | FK event_id: uuid | | | FK event_id: uuid | | FK event_id: uuid |
| | FK moderator_id: uuid | | | FK user_id: uuid [NULLABLE] | | FK space_id: uuid |
| | status: enum(pendiente, | | | user_name: varchar(255) | | FK requested_by_id: uuid |
| | aceptado, | | | user_cedula: varchar(20) | | requested_start_time: ts(tz) |
| | rechazado) | | | user_email: varchar(255) | | requested_end_time: ts(tz) |
| | created_at: timestamp | | | status: enum(confirmado,...) | | status: enum(pendiente,...) |
| +-----------------------------+ | | qr_token: varchar [UNIQUE] | | rejection_reason: text |
+---------------------------------+ | scanned_at: timestamp | +---------------------------------+
▲ +---------------------------------+
│ 1:N moderator_id │
│ │ 1:1 registration_id
(vía users.id) ▼
+---------------------------------+
| certificates |
+---------------------------------+
| PK id: uuid |
| FK registration_id: uuid [UNIQ] |
| verification_code: varchar |
| generated_at: timestamp |
+---------------------------------+3.3 Declaración de Esquemas de Drizzle ORM (TypeScript)
A continuación, se detalla la implementación lógica de los esquemas relacionales que interactúan de forma nativa con PostgreSQL mediante el ORM Drizzle:
import { pgTable, uuid, varchar, text, timestamp, boolean, integer, pgEnum } from 'drizzle-orm/pg-core';
// Definición de Enumerados Nativos de PostgreSQL
export const roleEnum = pgEnum('role', ['coordinador', 'moderador', 'organizador', 'usuario']);
export const eventStatusEnum = pgEnum('event_status', ['solicitado', 'en_revision', 'aprobado', 'rechazado', 'realizado', 'cancelado']);
export const spaceTypeEnum = pgEnum('space_type', ['auditorio', 'laboratorio', 'salon']);
export const requestStatusEnum = pgEnum('request_status', ['pendiente', 'aceptado', 'rechazado']);
export const registrationStatusEnum = pgEnum('registration_status', ['confirmado', 'cancelado']);
export const dateChangeRequestStatusEnum = pgEnum('date_change_request_status', ['pendiente', 'aprobado', 'rechazado']);
// 1. Tabla de Usuarios
export const users = pgTable('users', {
id: uuid('id').defaultRandom().primaryKey(),
fullName: varchar('full_name', { length: 255 }).notNull(),
email: varchar('email', { length: 255 }).notNull().unique(),
passwordHash: varchar('password_hash', { length: 255 }).notNull(),
role: roleEnum('role').default('usuario').notNull(),
cedula: varchar('cedula', { length: 20 }).unique(),
isActive: boolean('is_active').default(true).notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
});
// 2. Tabla de Espacios Académicos
export const spaces = pgTable('spaces', {
id: uuid('id').defaultRandom().primaryKey(),
name: varchar('name', { length: 255 }).notNull(),
type: spaceTypeEnum('type').notNull(),
capacity: integer('capacity').notNull(),
location: varchar('location', { length: 255 }).notNull(),
isActive: boolean('is_active').default(true).notNull(),
directions: text('directions'),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull()
});
// 3. Tabla de Eventos (Core Relacional)
export const events = pgTable('events', {
id: uuid('id').defaultRandom().primaryKey(),
title: varchar('title', { length: 500 }).notNull(),
description: text('description').notNull(),
type: varchar('type', { length: 100 }).notNull(),
status: eventStatusEnum('status').default('solicitado').notNull(),
startTime: timestamp('start_time', { withTimezone: true }).notNull(),
endTime: timestamp('end_time', { withTimezone: true }).notNull(),
spaceId: uuid('space_id').references(() => spaces.id, { onDelete: 'restrict' }).notNull(),
organizerId: uuid('organizer_id').references(() => users.id, { onDelete: 'restrict' }).notNull(),
estimatedAttendance: integer('estimated_attendance').notNull(),
rejectionReason: text('rejection_reason'),
hasCertificate: boolean('has_certificate').default(false).notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
});
// 4. Tabla de Solicitudes de Moderación (Resolución de relación M:N)
export const moderatorRequests = pgTable('moderator_requests', {
id: uuid('id').defaultRandom().primaryKey(),
eventId: uuid('event_id').references(() => events.id, { onDelete: 'cascade' }).notNull(),
moderatorId: uuid('moderator_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
status: requestStatusEnum('status').default('pendiente').notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
});
// 5. Tabla de Inscripciones y Control de Asistencia QR
export const registrations = pgTable('registrations', {
id: uuid('id').defaultRandom().primaryKey(),
eventId: uuid('event_id').references(() => events.id, { onDelete: 'cascade' }).notNull(),
userId: uuid('user_id').references(() => users.id, { onDelete: 'set null' }),
userName: varchar('user_name', { length: 255 }).notNull(),
userCedula: varchar('user_cedula', { length: 20 }).notNull(),
userEmail: varchar('user_email', { length: 255 }).notNull(),
status: registrationStatusEnum('status').default('confirmado').notNull(),
qrToken: varchar('qr_token', { length: 255 }).notNull().unique(),
attended: boolean('attended').default(false).notNull(),
scannedAt: timestamp('scanned_at', { withTimezone: true })
});
// 6. Tabla de Certificados Digitales Emitidos (Relación 1:1)
export const certificates = pgTable('certificates', {
id: uuid('id').defaultRandom().primaryKey(),
registrationId: uuid('registration_id').references(() => registrations.id, { onDelete: 'cascade' }).notNull().unique(),
verificationCode: varchar('verification_code', { length: 100 }).notNull().unique(),
generatedAt: timestamp('generated_at', { withTimezone: true }).defaultNow().notNull()
});
// 7. Tabla de Solicitudes de Cambio de Fecha (Reprogramaciones)
export const dateChangeRequests = pgTable('date_change_requests', {
id: uuid('id').primaryKey().defaultRandom(),
eventId: uuid('event_id').references(() => events.id, { onDelete: 'cascade' }).notNull(),
requestedStartTime: timestamp('requested_start_time', { withTimezone: true }).notNull(),
requestedEndTime: timestamp('requested_end_time', { withTimezone: true }).notNull(),
spaceId: uuid('space_id').references(() => spaces.id, { onDelete: 'cascade' }).notNull(),
status: dateChangeRequestStatusEnum('status').notNull().default('pendiente'),
rejectionReason: text('rejection_reason'),
requestedById: uuid('requested_by_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow()
});CAPÍTULO IV: ARQUITECTURA FÍSICA Y ENTORNO DE PRODUCCIÓN REAL
Para cumplir con el rigor de un proyecto formal y documentar con fidelidad el ecosistema donde corre la aplicación en el mundo real, se detallan los parámetros de infraestructura activos en el servidor privado virtual (VPS) del desarrollador:
Peticiones HTTPS en Internet
│
▼
[ Servidor Nginx (Puerto 80 / 443 SSL) ]
- Nginx aplica Proxy Inverso y SSL Certbot
│
┌─────────────────────┴─────────────────────┐
▼ (Dominio Frontend) ▼ (Dominio API)
https://facyt-events.sunlessteam.com https://api.facyt-events.sunlessteam.com
Redirecciona a puerto local Redirecciona a puerto local
│ │
▼ ▼
[ Next.js Standalone (PM2) ] [ NestJS REST API (PM2) ]
Puerto Local: 3008 Puerto Local: 3009
│
▼ (TCP Host: 5435)
[ Docker postgres:17-alpine ]
Puerto Interno: 54324.1 Detalle de Servidores y Puertos
- Servidor de Proxy Inverso (Nginx): Actúa como la puerta de enlace segura y punto de terminación SSL. Redirige el tráfico HTTPS de Internet hacia los procesos desacoplados locales gestionados por PM2 para evitar colisiones con otros entornos de la máquina.
- Frontend (Next.js 15 Standalone): Corre internamente en el puerto local 3008 bajo el proceso PM2
"facyt-events-frontend". Se expone al mundo real a través del subdominio de producción:https://facyt-events.sunlessteam.com. - Backend (NestJS 11 REST API): Corre internamente en el puerto local 3009 bajo el proceso PM2
"facyt-events-backend". Se expone de forma cifrada a través de:https://api.facyt-events.sunlessteam.com. - Base de Datos (PostgreSQL 17-alpine): Se encuentra encapsulada de forma segura en un contenedor Docker aislado (
facyt-postgres) exponiendo internamente el puerto estándar 5432 pero mapeado al puerto host 5435 para el consumo privado y local del backend.
4.2 Variables de Entorno Activas en Producción
#### Variables del Backend (NestJS .env):
# Base de Datos PostgreSQL
DATABASE_URL=postgresql://ejemplo:ejemplo_password@localhost:ejemplo/ejemplo
# Configuración de Seguridad JWT
JWT_SECRET=ejemplo
JWT_EXPIRES_IN=7d
# Servidor SMTP de Notificación Académica (Nodemailer)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=ejemplo
SMTP_USER=ejemplo
SMTP_PASS=ejemplo
# Parámetros del Proceso
PORT=3009
NODE_ENV=production#### Variables del Frontend (Next.js .env.local):
# Interconexión con la API REST de NestJS
NEXT_PUBLIC_API_URL=https://api.facyt-events.sunlessteam.com/api/v1
# Seguridad del Cliente y NextAuth
NEXTAUTH_SECRET=ejemplo
NEXTAUTH_URL=https://facyt-events.sunlessteam.comCAPÍTULO V: INTEGRACIÓN DE ENTREGABLES COMO VISTAS APLICATIVAS
Con el propósito de elevar el rigor académico del proyecto y facilitar la evaluación directa e interactiva por parte de la cátedra de Sistemas de Información, se ha tomado una decisión de diseño de sistemas innovadora: integrar los tres entregables obligatorios de la asignación directamente en el cliente web Next.js como rutas públicas navegables.
HEADER NAV MENÚ PÚBLICO
┌──────────┬─────────────────┬─────────────┐
│ Inicio │ Documentación │ Ingresar │
└──────────┴────────┬────────┴─────────────┘
│
▼
RUTA PÚBLICA: /documentacion
┌──────────────────────────────────────────┐
│ VISTA DE PESTAÑAS (TABS SELECTOR) |
│ ┌──────────────┐┌──────────────┐┌───────┐ │
│ │ Entregable ││ Entregable ││ Fact. │ │
│ │ A (SI) ││ B (TI) ││ IA │ │
│ └──────────────┘└──────────────┘└───────┘ │
│ │
│ [ Contenido dinámico con Markdown, ] │
│ [ esquemas en bloques, ERD e íconos ] │
└──────────────────────────────────────────┘5.1 Estructura de la Ruta /documentacion
Se ha diseñado una interfaz unificada bajo la ruta pública /documentacion que organiza los entregables de forma dinámica mediante un selector de pestañas interactivo (Tabs) con estética Glassmorphism Premium:
- Pestaña I: Análisis de Sistema (Entregable A): Renderiza dinámicamente todo el contenido conceptual del problema identificado en la FaCyT, la justificación institucional, la delimitación de fronteras, la matriz de actores y roles, el mapeo de componentes sistémicos y el workflow de estados del ciclo de vida de los eventos.
- Pestaña II: Diseño de Arquitectura (Entregable B): Expone de forma gráfica e interactiva la arquitectura de tres capas del Polyrepo, el diagrama entidad-relación (ERD) unificado con la tabla intermedia
moderator_requestsy la tabladate_change_requests, los esquemas TypeScript declarados para Drizzle ORM, el mapeo de puertos de producción en el VPS y los endpoints REST configurados. - Pestaña III: Factor IA y Reflexión Ética (Entregable C): Ilustra detalladamente la bitácora de prompts con los agentes de Antigravity, los seis casos de control crítico y depuración de alucinaciones lógicas (Drizzle, Timezones, Multer, bloqueo síncrono del SMTP, cooldown del QR y la validación de aforo blanda con
useConfirm), el debate ético sobre los límites de la automatización en FaCyT y la declaración de autoría.
5.2 Justificación de Diseño y Experiencia de Usuario (UX)
Esta integración aporta un valor único a la presentación presencial y defensa del proyecto:
- Evaluación in situ: El jurado o profesor evaluador puede constatar la solidez conceptual y metodológica del sistema directamente en producción navegando en la web activa (https://facyt-events.sunlessteam.com/documentacion).
- Consistencia Visual: En lugar de leer un archivo PDF estático, la documentación se adapta perfectamente al tema oscuro premium de la aplicación, incorporando efectos de Glassmorphism, íconos de Lucide React altamente legibles y tipografías responsivas, garantizando que la documentación técnica sea un componente vivo del software desarrollado.
CONCLUSIÓN SISTÉMICA DEL TRABAJO
Este informe técnico evidencia que la plataforma FaCyT Events no es un CRUD plano o una maqueta aislada, sino una solución sistémica formalmente fundamentada, técnicamente madura y desplegada exitosamente en producción. Al combinar un motor de base de datos relacional robusto con transacciones ACID para evitar colisiones horarias físicas (limitadas a eventos aprobados para permitir la coexistencia de propuestas en revisión), flujos dinámicos de reprogramación (date_change_requests) y de delegación de moderadores (moderator_requests), un sistema asíncrono de retroalimentación vía correo electrónico con soporte para QR inline (cid), y una experiencia de check-in continuo y rápido con cooldown, el sistema responde de manera directa a los retos operativos reales de la FaCyT, garantizando su validez y pertinencia de cara a la defensa de la asignatura.