Skip to content

bryanstevensacosta/bookings-software

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

432 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Sistema de Reservas Multi-Tenant vía WhatsApp

CI Pipeline CodeQL License: MIT

Sistema de gestión de citas automatizado a través de WhatsApp Business API, construido con NestJS, TypeScript, PostgreSQL y siguiendo principios de Clean Architecture, DDD y CQRS.

🚀 Características Principales

  • Reservaciones Automatizadas: Flujo conversacional completo vía WhatsApp
  • Multi-Tenant: Soporte para múltiples negocios en una sola instancia
  • CQRS Estricto: Separación completa entre comandos y queries
  • Event-Driven: Arquitectura basada en eventos de dominio
  • Optimistic Locking: Manejo de concurrencia con versioning de aggregates
  • Property-Based Testing: Tests exhaustivos con fast-check
  • Clean Architecture: Separación clara de capas (Domain, Application, Infrastructure, Presentation)

📋 Requisitos Previos

  • Node.js: v18 o superior
  • PostgreSQL: v14 o superior
  • npm: v9 o superior

🛠️ Instalación

1. Clonar el Repositorio

git clone https://github.com/cryptoganster/bookings-software.git
cd bookings-software

2. Instalar Dependencias

npm install

3. Configurar Variables de Entorno

Copiar el archivo de ejemplo y configurar las variables:

cp .env.example .env

Editar .env con tus configuraciones:

# Application
NODE_ENV=development
PORT=3000

# Database
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=postgres
DB_PASSWORD=tu_password
DB_DATABASE=postgres_dev

# JWT
JWT_SECRET=tu_secret_key_muy_seguro
JWT_EXPIRATION=1d

# WhatsApp Business API (opcional para desarrollo)
WHATSAPP_API_URL=https://graph.facebook.com/v18.0/PHONE_NUMBER_ID
WHATSAPP_ACCESS_TOKEN=tu_token
WHATSAPP_WEBHOOK_VERIFY_TOKEN=tu_verify_token

# Logging
LOG_LEVEL=debug

4. Configurar Base de Datos

Opción A: Usando Docker (Recomendado)

# Iniciar PostgreSQL con Docker Compose
docker-compose -f docker-compose.dev.yml up -d

# La base de datos estará disponible en localhost:5432

Opción B: PostgreSQL Local

Crear la base de datos manualmente:

psql -U postgres
CREATE DATABASE postgres_dev;
CREATE DATABASE postgres_test;
\q

5. Ejecutar Migraciones

# Ejecutar migraciones
npm run migration:run

# Verificar estado de migraciones
npm run migration:show

# Ejecutar seeders (datos de prueba)
npm run seed

📊 Database Management

Migraciones

Las migraciones gestionan el esquema de la base de datos de forma versionada.

Crear Nueva Migración

# Generar migración automáticamente basada en cambios de entities
npm run migration:generate -- -n NombreDeLaMigracion

# Crear migración vacía para cambios manuales
npm run migration:create -- -n NombreDeLaMigracion

Ejecutar Migraciones

# Ejecutar todas las migraciones pendientes
npm run migration:run

# Ver estado de migraciones
npm run migration:show

# Revertir última migración
npm run migration:revert

Analizar Migraciones

# Ejecutar script de análisis para detectar problemas
npm run migration:analyze

El script de análisis detecta:

  • ✅ Migraciones con timestamps inválidos (deben ser 13 dígitos)
  • ✅ Migraciones duplicadas
  • ✅ Tablas faltantes en la base de datos

Estructura de Migraciones

Las migraciones están organizadas por Bounded Context:

src/database/migrations/
├── 1702550000000-EnableUuidExtension.ts          # Shared
├── 1702552000000-CreateUsersTable.ts             # Auth BC
├── 1734480000000-RefactorUserRoles.ts            # Auth BC
├── 1734481000000-StandardizeUsersTableNaming.ts  # Auth BC
├── 1734482000000-CreateCustomersTable.ts         # Customer BC
├── 1702553000000-CreateOfferingsTable.ts         # Offering BC
├── 1734650000000-CreateSchedulesTable.ts         # Availability BC
├── 1734650100000-CreateBlockoutsTable.ts         # Availability BC
├── 1702551100000-CreateCapacitiesTable.ts        # Availability BC
├── 1702551000000-CreateAppointmentsTable.ts      # Booking BC
├── 1734650200000-CreateConversationsTable.ts     # Conversation BC
├── 1734650300000-CreateMessagesTable.ts          # Conversation BC
├── 1734650400000-CreateBusinessOwnersTable.ts    # Account BC
├── 1734650500000-CreateBusinessesTable.ts        # Business BC
└── 1766345899000-AddSearchIndexesToCustomers.ts  # Customer BC

Documentación completa: apps/backend/src/database/MIGRATIONS.md

Seeds

Los seeds poblan la base de datos con datos de prueba para desarrollo.

Ejecutar Seeds

# Ejecutar todos los seeds en orden
npm run seed

# Los seeds se ejecutan en este orden:
# 1. auth (users)
# 2. account (business_owners)
# 3. business (businesses)
# 4. customer (customers)
# 5. offering (offerings)
# 6. availability (schedules, blockouts, capacities)
# 7. booking (appointments)
# 8. conversation (conversations, messages)

Datos Generados por Seeds

Tabla Registros Descripción
users 2 1 BUSINESS_OWNER, 1 CUSTOMER
business_owners 2 1 FREE plan, 1 PRO plan
businesses 1 Peluquería Central (activo)
customers 25 12 anónimos, 8 registrados, 5 merged
offerings 7 6 activos (15-90 min), 1 inactivo
schedules 6 Lun-Vie 9am-6pm, Sáb 10am-2pm, Dom cerrado
blockouts 3 Navidad, Año Nuevo, Vacaciones de verano
capacities ~78 30 días de capacidad para cada offering
appointments ~35 23 CONFIRMED, 5 CANCELLED, 7 COMPLETED
conversations 8 3 ACTIVE, 2 AWAITING_ADMIN, 3 RESOLVED
messages 29 TEXT, BUTTON, LOCATION types

Documentación completa: apps/backend/src/database/SEEDS.md

Verificar Datos de Seeds

# Conectar a la base de datos
docker exec -it <container-id> psql -U postgres -d bookings-software

# Verificar conteo de registros
SELECT 'users' as table_name, COUNT(*) FROM users
UNION ALL
SELECT 'business_owners', COUNT(*) FROM business_owners
UNION ALL
SELECT 'businesses', COUNT(*) FROM businesses
UNION ALL
SELECT 'customers', COUNT(*) FROM customers
UNION ALL
SELECT 'offerings', COUNT(*) FROM offerings
UNION ALL
SELECT 'schedules', COUNT(*) FROM schedules
UNION ALL
SELECT 'blockouts', COUNT(*) FROM blockouts
UNION ALL
SELECT 'capacities', COUNT(*) FROM capacities
UNION ALL
SELECT 'appointments', COUNT(*) FROM appointments
UNION ALL
SELECT 'conversations', COUNT(*) FROM conversations
UNION ALL
SELECT 'messages', COUNT(*) FROM messages;

Troubleshooting Database

Error: "Cannot connect to database"

# Verificar que PostgreSQL esté corriendo
docker-compose -f docker-compose.dev.yml ps

# Ver logs de PostgreSQL
docker-compose -f docker-compose.dev.yml logs postgres

# Reiniciar PostgreSQL
docker-compose -f docker-compose.dev.yml restart postgres

Error: "Migration failed"

# Revertir última migración
npm run migration:revert

# Volver a ejecutar
npm run migration:run

# Si persiste, verificar logs y estado de la base de datos
npm run migration:show

Error: "Seed failed"

# Los seeds usan TRUNCATE CASCADE, así que son seguros de re-ejecutar
npm run seed

# Si hay errores de foreign keys, verificar orden de ejecución en:
# apps/backend/src/database/seeds/seed.ts

Limpiar Base de Datos

# Revertir todas las migraciones
npm run migration:revert

# Volver a ejecutar todo
npm run migration:run
npm run seed

Verificar Integridad de Datos

# Ejecutar tests de integridad de base de datos
npm run test -- database/__tests__

# Tests incluyen:
# - Validación de migraciones (timestamps, duplicados, tablas)
# - Validación de seeds (conteos, foreign keys, variedad de datos)

🚀 Ejecución

Modo Desarrollo

# Iniciar con hot-reload
npm run start:dev

# La aplicación estará disponible en http://localhost:3000

Modo Producción

# Compilar TypeScript
npm run build

# Iniciar versión compilada
npm run start:prod

Modo Debug

# Iniciar con debugger
npm run start:debug

🧪 Testing

Tests Unitarios

# Ejecutar todos los tests unitarios
npm run test

# Ejecutar tests en modo watch
npm run test:watch

# Ejecutar tests con cobertura
npm run test:cov

Tests E2E

# Ejecutar tests end-to-end
npm run test:e2e

# Ejecutar test específico
npm run test:e2e -- conversation-flow.e2e-spec.ts

Tests de Property-Based

Los tests de property-based están integrados en los tests unitarios y usan fast-check:

# Ejecutar tests que incluyen property-based tests
npm run test -- --testPathPattern="pbt.spec.ts"

📁 Estructura del Proyecto

src/
├── shared/                    # Shared Kernel
│   ├── kernel/               # Abstracciones base (VersionedAggregateRoot, ValueObject)
│   ├── vo/                   # Value Objects compartidos (UUID, AggregateVersion)
│   └── infra/                # Implementaciones compartidas (UnitOfWork)
│
├── auth/                     # Bounded Context: Autenticación
│   ├── domain/              # User aggregate, Events, Value Objects (UserRole enum)
│   ├── app/                 # Commands (Register, Login, AddRole, RemoveRole), Queries, Event Handlers
│   ├── infra/               # Repositories, Mappers, Guards (JwtAuthGuard, RolesGuard), JWT Strategy
│   └── presentation/        # Controllers, DTOs (RegisterDto, AddUserRoleDto)
│
├── availability/            # Bounded Context: Disponibilidad
│   ├── domain/             # Capacity aggregate, Events
│   ├── app/                # Commands, Queries
│   ├── infra/              # Repositories, Factories
│   └── presentation/       # (vacío por ahora)
│
├── booking/                # Bounded Context: Reservaciones ⭐
│   ├── domain/            # Appointment aggregate, Events, Exceptions
│   ├── app/               # Commands, Queries, Event Handlers, Sagas
│   ├── infra/             # Repositories, Mappers
│   └── presentation/      # Controllers
│
└── conversation/          # Bounded Context: Conversaciones WhatsApp
    ├── domain/           # Conversation aggregate, Events
    ├── app/              # Commands, Queries
    ├── infra/            # WhatsApp Client
    └── presentation/     # Webhook Controller

🏗️ Arquitectura

Principios Aplicados

  • Clean Architecture: Separación de capas con dependencias hacia el dominio
  • Domain-Driven Design (DDD): Bounded Contexts, Aggregates, Value Objects, Domain Events
  • CQRS: Separación estricta entre Commands (escritura) y Queries (lectura)
  • Event-Driven: Comunicación entre Bounded Contexts vía Domain Events
  • Optimistic Locking: Control de concurrencia con versioning

Bounded Contexts

  1. Auth: Gestión de autenticación y autorización con roles múltiples (JWT)
    • Soporte para roles: BUSINESS_OWNER, CUSTOMER, ADMIN
    • Un usuario puede tener múltiples roles simultáneamente
    • Gestión de roles: agregar/remover roles dinámicamente
    • Verificación de email y activación/desactivación de cuentas
  2. Availability: Gestión de capacidad y horarios disponibles
  3. Booking: Gestión de citas y reservaciones (BC principal)
  4. Conversation: Integración con WhatsApp y flujo conversacional

Patrones Implementados

  • Repository Pattern: Abstracción de persistencia
  • Unit of Work: Gestión de transacciones
  • Factory Pattern: Creación de aggregates complejos
  • Saga Pattern: Orquestación de procesos largos
  • CQRS: CommandBus, QueryBus, EventBus de NestJS

📚 Comandos Disponibles

Desarrollo

npm run start:dev          # Iniciar con hot-reload
npm run start:debug        # Iniciar con debugger
npm run lint               # Ejecutar ESLint
npm run format             # Formatear con Prettier

Base de Datos

npm run migration:generate # Generar migración
npm run migration:run      # Ejecutar migraciones
npm run migration:revert   # Revertir última migración
npm run migration:show     # Ver estado de migraciones
npm run migration:analyze  # Analizar migraciones (detectar problemas)
npm run seed               # Ejecutar seeders

Testing

npm run test               # Tests unitarios
npm run test:watch         # Tests en modo watch
npm run test:cov           # Tests con cobertura
npm run test:e2e           # Tests end-to-end

Producción

npm run build              # Compilar TypeScript
npm run start:prod         # Iniciar versión compilada

🔧 Agregar Nuevos Bounded Contexts

Para agregar un nuevo Bounded Context siguiendo el patrón de Booking:

1. Crear Estructura de Carpetas

mkdir -p src/nuevo-bc/{domain,app,infra,presentation}
mkdir -p src/nuevo-bc/domain/{aggregates,events,vo,exceptions,interfaces}
mkdir -p src/nuevo-bc/app/{commands,queries,event-handlers,sagas}
mkdir -p src/nuevo-bc/infra/{persistence,external}
mkdir -p src/nuevo-bc/presentation/controllers

2. Crear Aggregate

// src/nuevo-bc/domain/aggregates/mi-aggregate.ts
import { VersionedAggregateRoot } from "@shared/kernel/versioned-aggregate-root";
import { UUID } from "@shared/vo/uuid";

export class MiAggregate extends VersionedAggregateRoot {
  private id: UUID;

  static create(id: UUID, ...params): MiAggregate {
    const aggregate = new MiAggregate();
    aggregate.id = id;
    aggregate.incrementVersion();
    aggregate.apply(new MiAggregateCreated(id.getValue()));
    return aggregate;
  }

  // Métodos de negocio...
}

3. Crear Command y Handler

// src/nuevo-bc/app/commands/mi-command/command.ts
import { Command } from "@nestjs/cqrs";

export class MiCommand extends Command<{ id: string }> {
  constructor(public readonly param: string) {
    super();
  }
}

// src/nuevo-bc/app/commands/mi-command/handler.ts
import { CommandHandler, ICommandHandler } from "@nestjs/cqrs";

@CommandHandler(MiCommand)
export class MiCommandHandler implements ICommandHandler<MiCommand> {
  async execute(command: MiCommand): Promise<{ id: string }> {
    // Implementación...
  }
}

4. Crear Módulo NestJS

// src/nuevo-bc/nuevo-bc.module.ts
import { Module } from "@nestjs/common";
import { CqrsModule } from "@nestjs/cqrs";

@Module({
  imports: [CqrsModule],
  providers: [
    // Command Handlers
    MiCommandHandler,
    // Query Handlers
    // Event Handlers
    // Repositories
  ],
  exports: [],
})
export class NuevoBcModule {}

5. Registrar en AppModule

// src/app.module.ts
import { NuevoBcModule } from "./nuevo-bc/nuevo-bc.module";

@Module({
  imports: [
    // ...otros módulos
    NuevoBcModule,
  ],
})
export class AppModule {}

🐛 Troubleshooting

Error: "Cannot connect to database"

Verificar que PostgreSQL esté corriendo y las credenciales sean correctas:

# Verificar estado de PostgreSQL
docker-compose -f docker-compose.dev.yml ps

# Ver logs de PostgreSQL
docker-compose -f docker-compose.dev.yml logs postgres

Error: "Migration failed"

Revertir migraciones y volver a ejecutar:

npm run migration:revert
npm run migration:run

Tests Fallando

Limpiar base de datos de test y volver a ejecutar:

npm run test:e2e

Para más detalles sobre troubleshooting de base de datos, ver la sección Database Management arriba.

🔄 CI/CD & DevSecOps

El proyecto implementa un pipeline completo de CI/CD con seguridad integrada:

Pipeline de CI

El pipeline de CI se ejecuta automáticamente en cada push y pull request:

  • Linting: ESLint en backend y frontend
  • Formatting: Prettier check
  • Type Checking: TypeScript compilation
  • Security Audit: npm audit para vulnerabilidades
  • License Check: Verificación de licencias compatibles
  • Secret Scanning: TruffleHog para detectar secretos expuestos
  • Tests: Jest (backend) y Vitest (frontend) con cobertura
  • Build: Compilación de backend y frontend
  • CodeQL: Análisis estático de seguridad (SAST)

Herramientas de Seguridad

  • CodeQL: Análisis estático de código (SAST)
  • Dependabot: Actualizaciones automáticas de dependencias
  • Secret Scanning: Detección de secretos en código
  • npm audit: Escaneo de vulnerabilidades en dependencias
  • TruffleHog: Escaneo de secretos en historial de git

Ejecutar CI Localmente

# Instalar act (GitHub Actions local runner)
brew install act  # macOS
# o
curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash

# Ejecutar pipeline de CI
act push

# Ejecutar job específico
act push -j lint

Documentación CI/CD

📖 Documentación Adicional

🤝 Contribuir

  1. Fork el proyecto
  2. Crear una rama para tu feature (git checkout -b feature/amazing-feature)
  3. Commit tus cambios (git commit -m 'feat: add amazing feature')
  4. Push a la rama (git push origin feature/amazing-feature)
  5. Abrir un Pull Request

📝 Licencia

Este proyecto está bajo la Licencia MIT. Ver el archivo LICENSE para más detalles.

👥 Autores

🔐 Autenticación y Autorización

Sistema de Roles

El sistema implementa un modelo de autenticación basado en roles múltiples, donde un usuario puede tener varios roles simultáneamente:

Roles Disponibles

  • BUSINESS_OWNER: Dueño de negocio, puede administrar servicios, horarios y ver citas
  • CUSTOMER: Cliente que agenda citas (futuro: panel web para clientes)
  • ADMIN: Administrador del sistema con permisos completos

Registro de Usuario

POST /api/auth/register
Content-Type: application/json

{
  "email": "usuario@example.com",
  "password": "password123",
  "name": "Juan Pérez",
  "initialRole": "BUSINESS_OWNER"  // Opcional, default: BUSINESS_OWNER
}

Respuesta:

{
  "user": {
    "id": "uuid",
    "email": "usuario@example.com",
    "name": "Juan Pérez",
    "roles": ["BUSINESS_OWNER"],
    "isActive": true,
    "emailVerified": false
  },
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Login

POST /api/auth/login
Content-Type: application/json

{
  "email": "usuario@example.com",
  "password": "password123"
}

Respuesta:

{
  "user": {
    "id": "uuid",
    "email": "usuario@example.com",
    "name": "Juan Pérez",
    "roles": ["BUSINESS_OWNER", "CUSTOMER"],
    "isActive": true,
    "emailVerified": true
  },
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

JWT Payload

El token JWT contiene:

{
  "userId": "uuid",
  "email": "usuario@example.com",
  "roles": ["BUSINESS_OWNER", "CUSTOMER"],
  "iat": 1734480000,
  "exp": 1734566400
}

Nota: El JWT NO contiene businessId. Para obtener el negocio de un usuario, usar el endpoint correspondiente del Business BC.

Gestión de Roles

Agregar Rol a Usuario

POST /api/auth/users/:userId/roles
Authorization: Bearer {token}
Content-Type: application/json

{
  "role": "CUSTOMER"
}

Reglas:

  • No se puede agregar un rol que el usuario ya tiene
  • El usuario debe existir y estar activo

Remover Rol de Usuario

DELETE /api/auth/users/:userId/roles/:role
Authorization: Bearer {token}

Reglas:

  • No se puede remover el último rol de un usuario
  • El usuario siempre debe tener al menos un rol

Autorización Basada en Roles

Proteger Endpoints con Guards

import { Controller, Get, UseGuards } from "@nestjs/common";
import { JwtAuthGuard } from "@auth/infra/guards/jwt-auth.guard";
import { RolesGuard } from "@auth/infra/guards/roles.guard";
import { Roles } from "@auth/presentation/decorators/roles.decorator";
import { UserRole } from "@auth/domain/vo/user-role";

@Controller("admin")
@UseGuards(JwtAuthGuard, RolesGuard)
export class AdminController {
  @Get("dashboard")
  @Roles(UserRole.ADMIN)
  getDashboard() {
    // Solo accesible para usuarios con rol ADMIN
    return { message: "Admin dashboard" };
  }

  @Get("business-stats")
  @Roles(UserRole.BUSINESS_OWNER, UserRole.ADMIN)
  getBusinessStats() {
    // Accesible para BUSINESS_OWNER o ADMIN
    return { message: "Business statistics" };
  }
}

Obtener Usuario Actual

import { Controller, Get, UseGuards } from "@nestjs/common";
import { JwtAuthGuard } from "@auth/infra/guards/jwt-auth.guard";
import { CurrentUser } from "@auth/presentation/decorators/current-user.decorator";
import { UserPayload } from "@auth/presentation/decorators/current-user.decorator";

@Controller("profile")
@UseGuards(JwtAuthGuard)
export class ProfileController {
  @Get()
  getProfile(@CurrentUser() user: UserPayload) {
    // user contiene: { userId, email, roles }
    return {
      id: user.userId,
      email: user.email,
      roles: user.roles,
    };
  }
}

Verificación de Email

POST /api/auth/verify-email
Authorization: Bearer {token}
Content-Type: application/json

{
  "userId": "uuid"
}

Reglas:

  • Solo se puede verificar una vez
  • Intentar verificar un email ya verificado lanza EmailAlreadyVerifiedException

Activación/Desactivación de Cuenta

Desactivar Usuario

POST /api/auth/users/:userId/deactivate
Authorization: Bearer {token}

Activar Usuario

POST /api/auth/users/:userId/activate
Authorization: Bearer {token}

Reglas:

  • Usuarios desactivados no pueden hacer login
  • Las operaciones son idempotentes (no fallan si ya están en ese estado)

Integración con Otros BCs

Account BC

Cuando un usuario se registra con rol BUSINESS_OWNER, el Account BC automáticamente:

  1. Escucha el evento UserRegistered
  2. Crea un BusinessOwner vinculado al usuario
  3. Asigna plan de suscripción inicial (FREE)

Customer BC

Cuando un cliente anónimo se vincula a un usuario:

  1. Customer BC publica evento CustomerLinkedToUser
  2. Auth BC escucha el evento
  3. Agrega automáticamente el rol CUSTOMER al usuario

Arquitectura de Identidades

El sistema sigue una arquitectura unificada de identidades:

User (Auth BC) → Identidad Universal
    ↓                           ↓
BusinessOwner (Account)    Customer (Customer)
    ↓                           ↓
Business (Business)        Appointment (Booking)

Beneficios:

  • Un usuario puede ser proveedor (BUSINESS_OWNER) y consumidor (CUSTOMER) simultáneamente
  • Preparado para marketplace: Juan (abogado) publica servicios Y agenda cita con dentista
  • Separación clara de concerns: User = autenticación, BusinessOwner = cuenta, Business = negocio

Para más detalles, ver: .kiro/steering/user-customer-businessowner-architecture.md

📡 WebSocket Events API

El sistema emite eventos en tiempo real vía WebSocket para notificar cambios a los clientes conectados.

Conexión

import { io } from "socket.io-client";

const socket = io("http://localhost:3000", {
  auth: {
    token: "your-jwt-token",
  },
});

// El servidor automáticamente une al cliente a la room de su negocio
// Room: business:{businessId}

Eventos de Offering

offering:created

Emitido cuando se crea un nuevo servicio.

Payload:

{
  offeringId: string; // UUID del offering
  name: string; // Nombre del servicio
  durationMinutes: number; // Duración en minutos
  maxCapacityPerSlot: number; // Capacidad máxima por slot
  maxDailyCapacity: number | null; // Límite diario (opcional)
  timestamp: string; // ISO 8601 timestamp
}

Ejemplo:

socket.on("offering:created", (data) => {
  console.log("Nuevo servicio creado:", data);
  // Actualizar UI, invalidar cache, etc.
});

offering:updated

Emitido cuando se actualiza un servicio existente.

Payload:

{
  offeringId: string; // UUID del offering
  name: string; // Nombre actualizado
  durationMinutes: number; // Duración actualizada
  maxCapacityPerSlot: number; // Capacidad actualizada
  maxDailyCapacity: number | null; // Límite diario actualizado
  timestamp: string; // ISO 8601 timestamp
}

Ejemplo:

socket.on("offering:updated", (data) => {
  console.log("Servicio actualizado:", data);
  // Actualizar servicio en UI
});

offering:deactivated

Emitido cuando se desactiva un servicio.

Payload:

{
  offeringId: string; // UUID del offering desactivado
  timestamp: string; // ISO 8601 timestamp
}

Ejemplo:

socket.on("offering:deactivated", (data) => {
  console.log("Servicio desactivado:", data);
  // Marcar servicio como inactivo en UI
});

offering:activated

Emitido cuando se reactiva un servicio previamente desactivado.

Payload:

{
  offeringId: string; // UUID del offering activado
  timestamp: string; // ISO 8601 timestamp
}

Ejemplo:

socket.on("offering:activated", (data) => {
  console.log("Servicio activado:", data);
  // Marcar servicio como activo en UI
});

Eventos de Appointment

appointment:created

Emitido cuando se crea una nueva cita.

Payload:

{
  appointmentId: string; // UUID de la cita
  customerId: string; // UUID del cliente
  offeringId: string; // UUID del servicio
  dateTime: string; // ISO 8601 timestamp de la cita
  timestamp: string; // ISO 8601 timestamp del evento
}

appointment:cancelled

Emitido cuando se cancela una cita.

Payload:

{
  appointmentId: string; // UUID de la cita cancelada
  timestamp: string; // ISO 8601 timestamp
}

Nota: Este evento se broadcast a todos los clientes conectados (no solo al negocio) debido a limitaciones del evento de dominio. Los clientes deben filtrar por appointmentId.

appointment:modified

Emitido cuando se modifica una cita existente.

Payload:

{
  appointmentId: string; // UUID de la cita
  newDateTime: string; // Nueva fecha/hora (ISO 8601)
  timestamp: string; // ISO 8601 timestamp
}

Nota: Este evento se broadcast a todos los clientes conectados (no solo al negocio) debido a limitaciones del evento de dominio. Los clientes deben filtrar por appointmentId.

Multi-Tenancy

Los eventos de Offering se emiten solo a los clientes del mismo negocio (room business:{businessId}), garantizando aislamiento de datos entre tenants.

Los eventos de Appointment actualmente se emiten a todos los clientes debido a limitaciones en los eventos de dominio (no incluyen businessId). Esto será mejorado en versiones futuras.

Manejo de Errores

socket.on("connect_error", (error) => {
  console.error("Error de conexión:", error);
});

socket.on("error", (error) => {
  console.error("Error de WebSocket:", error);
});

Ejemplo Completo

import { io } from "socket.io-client";

const socket = io("http://localhost:3000", {
  auth: {
    token: localStorage.getItem("jwt-token"),
  },
});

// Escuchar eventos de offerings
socket.on("offering:created", (data) => {
  // Agregar nuevo offering a la lista
  addOfferingToUI(data);
});

socket.on("offering:updated", (data) => {
  // Actualizar offering en la lista
  updateOfferingInUI(data);
});

socket.on("offering:deactivated", (data) => {
  // Marcar como inactivo
  markOfferingAsInactive(data.offeringId);
});

socket.on("offering:activated", (data) => {
  // Marcar como activo
  markOfferingAsActive(data.offeringId);
});

// Escuchar eventos de appointments
socket.on("appointment:created", (data) => {
  // Agregar nueva cita al calendario
  addAppointmentToCalendar(data);
});

socket.on("appointment:cancelled", (data) => {
  // Remover cita del calendario
  removeAppointmentFromCalendar(data.appointmentId);
});

socket.on("appointment:modified", (data) => {
  // Actualizar cita en el calendario
  updateAppointmentInCalendar(data);
});

// Manejo de errores
socket.on("connect_error", (error) => {
  console.error("Error de conexión:", error);
  showErrorNotification("No se pudo conectar al servidor");
});

🙏 Agradecimientos

  • NestJS por el excelente framework
  • La comunidad de DDD y CQRS por los patrones y mejores prácticas
  • fast-check por la librería de property-based testing

About

Multi-tenant booking platform built with NestJS, Clean Architecture, Domain Driven Design and Command Query Responsibility Segregation, providing automated appointment scheduling, notifications, and business administration.

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors