# Glosario de Términos - ERP Construcción SaaS **Versión:** 2.0 SaaS **Fecha:** 2025-11-17 **Propósito:** Unificar terminología entre documentación técnica, negocio y código --- ## 🎯 Términos Clave ### Multi-tenancy Concepts #### Tenant (Término Técnico SaaS) **Definición:** Entidad de aislamiento lógico en una arquitectura SaaS multi-tenant. En nuestro sistema, **tenant = constructora**. **Uso:** - **Documentación técnica:** Usar cuando se habla de arquitectura SaaS genérica - **Código:** Usar en nombres de variables/funciones cuando el contexto es claro - **Comunicación con clientes:** ❌ Evitar, usar "constructora" en su lugar **Sinónimos:** - Constructora (término de negocio) - Cliente (en contexto de suscripción SaaS) - Empresa (en contexto general) **Ejemplos:** ```typescript // ✅ Aceptable en código técnico interface TenantConfig { id: string; plan: SubscriptionPlan; } // ✅ Mejor para dominio de negocio interface ConstructoraConfig { id: string; plan: SubscriptionPlan; } ``` --- #### Constructora (Término de Negocio) **Definición:** Empresa de construcción que es cliente del sistema SaaS. Es el equivalente de "tenant" en el dominio de negocio. **Uso:** - **Documentación de negocio:** Usar siempre - **Comunicación con usuarios:** Usar siempre - **Base de datos:** Tabla `constructoras.constructoras` - **API:** Endpoint `/api/constructoras` **Atributos principales:** - `id` (UUID): Identificador único - `nombre`: Nombre comercial (ej: "Constructora ABC") - `razon_social`: Razón social legal - `rfc`: Registro Federal de Contribuyentes (México) - `subdomain`: Subdominio asignado (ej: "constructora-abc") - `plan`: Plan de suscripción (Básico, Profesional, Enterprise) **Ejemplos:** ```sql -- ✅ Tabla en BD CREATE TABLE constructoras.constructoras ( id UUID PRIMARY KEY, nombre VARCHAR(255) NOT NULL, subdomain VARCHAR(100) UNIQUE NOT NULL, active BOOLEAN DEFAULT TRUE ); ``` ```typescript // ✅ En servicios de negocio export class ConstructoraService { async findBySubdomain(subdomain: string): Promise { // ... } } ``` **Relación con usuarios:** - Un usuario puede pertenecer a múltiples constructoras - La relación se gestiona en `auth_management.user_constructoras` - Cada relación tiene un rol específico (director, engineer, resident, etc.) --- ### Modelo de Aislamiento #### Row-Level Security (RLS) **Definición:** Mecanismo de PostgreSQL que aplica políticas de seguridad a nivel de fila para aislar datos entre constructoras. **Funcionamiento:** 1. Cada tabla multi-tenant tiene columna `constructora_id` 2. Se configuran RLS policies que filtran por `constructora_id` 3. El contexto se establece por sesión: `app.current_constructora_id` 4. PostgreSQL aplica automáticamente las políticas **Ventajas:** - ✅ Aislamiento lógico robusto - ✅ Escalabilidad ilimitada - ✅ Migraciones simples - ✅ Reutilización de código GAMILIT (90%) **Ejemplo:** ```sql -- Política RLS en tabla projects CREATE POLICY "projects_select_own_constructora" ON projects.projects FOR SELECT TO authenticated USING ( constructora_id::text = current_setting('app.current_constructora_id', true) ); ``` ```typescript // Establecer contexto al inicio del request await dataSource.query(` SELECT set_config('app.current_constructora_id', $1, true) `, [constructoraId]); ``` --- #### Columna Discriminadora **Definición:** Columna `constructora_id` (tipo UUID) presente en todas las tablas que contienen datos específicos de una constructora. **Uso:** - **Foreign Key:** Apunta a `constructoras.constructoras(id)` - **Indexada:** Para performance en queries filtrados - **NOT NULL:** En tablas multi-tenant - **Nullable:** En tablas compartidas (catálogos globales) **Tablas con discriminador:** ```sql -- Ejemplos projects.projects.constructora_id budgets.budgets.constructora_id purchases.purchase_orders.constructora_id hr.employees.constructora_id auth_management.profiles.constructora_id ``` **Tablas SIN discriminador (compartidas):** ```sql -- Catálogos globales public.countries public.currencies public.units_of_measure ``` --- ### Roles y Permisos #### Roles de Construcción **Definición:** 7 roles específicos del dominio de construcción, definidos en ENUM `construction_role`. **Lista completa:** 1. **`director`**: Director general/proyectos 2. **`engineer`**: Ingeniero de planeación/control 3. **`resident`**: Residente de obra/supervisor 4. **`purchases`**: Compras/almacén 5. **`finance`**: Administración/finanzas 6. **`hr`**: Recursos humanos 7. **`post_sales`**: Postventa/garantías **Uso en RLS:** ```sql CREATE FUNCTION get_current_user_role() RETURNS TEXT AS $$ BEGIN RETURN current_setting('app.current_user_role', true); END; $$ LANGUAGE plpgsql STABLE; -- Política que considera el rol CREATE POLICY "budgets_directors_see_margins" ON budgets.budgets FOR SELECT TO authenticated USING ( constructora_id::text = current_setting('app.current_constructora_id', true) AND ( get_current_user_role() IN ('director', 'finance') OR hide_margins = false ) ); ``` --- #### Relación Usuario-Constructora **Definición:** Asociación many-to-many entre usuarios y constructoras, gestionada en `auth_management.user_constructoras`. **Atributos:** - `user_id`: FK a `auth_management.profiles` - `constructora_id`: FK a `constructoras.constructoras` - `role`: Rol del usuario en esta constructora - `is_primary`: Si es la constructora principal del usuario - `active`: Si la relación está activa **Casos de uso:** 1. Usuario trabaja en múltiples constructoras 2. Cambio de rol sin perder historial 3. Desactivación temporal (sin borrado) 4. Usuario externo (consultor, auditor) **Ejemplo:** ```typescript // Usuario puede tener múltiples constructoras const userConstructoras = await db.query(` SELECT c.*, uc.role, uc.is_primary FROM constructoras.constructoras c INNER JOIN auth_management.user_constructoras uc ON uc.constructora_id = c.id WHERE uc.user_id = $1 AND uc.active = true `, [userId]); // Usuario selecciona constructora activa al login const switchConstructora = async (userId, constructoraId) => { // Validar acceso const hasAccess = await userHasAccessToConstructora(userId, constructoraId); if (!hasAccess) throw new ForbiddenException(); // Actualizar JWT con nuevo constructoraId return generateJWT({ userId, constructoraId, role }); }; ``` --- ### Arquitectura SaaS #### Subdominio **Definición:** Identificador único en formato de subdominio DNS usado para acceder a la instancia de una constructora. **Formato:** ``` https://{subdomain}.erp-construccion.com ``` **Ejemplos:** - `constructora-abc.erp-construccion.com` - `viviendas-xyz.erp-construccion.com` - `desarrollos-norte.erp-construccion.com` **Validaciones:** - Solo lowercase, números y guiones - Sin espacios ni caracteres especiales - Único en toda la plataforma - Mínimo 3, máximo 50 caracteres **Lookup:** ```typescript // Resolver constructora desde subdomain const subdomain = req.hostname.split('.')[0]; // "constructora-abc" const constructora = await db.query(` SELECT * FROM constructoras.constructoras WHERE subdomain = $1 `, [subdomain]); ``` --- #### Plan de Suscripción **Definición:** Nivel de servicio contratado por la constructora. **Opciones:** 1. **Básico** ($399/mes): 10 usuarios, 6 módulos core 2. **Profesional** ($799/mes): 25 usuarios, 12 módulos 3. **Enterprise** ($1,499/mes): 100 usuarios, todos los módulos (18) **Atributos por plan:** - Límite de usuarios - Módulos incluidos - Módulos disponibles como add-on - Almacenamiento - Nivel de soporte - SLA de uptime **Activación de módulos:** ```typescript // Feature flag basado en plan export class FeatureFlagsService { async isModuleEnabled( moduleCode: string, constructoraId: string ): Promise { const constructora = await this.getConstructora(constructoraId); // Verificar si módulo está incluido en el plan const planModules = this.getModulesForPlan(constructora.plan); if (planModules.includes(moduleCode)) return true; // Verificar si está habilitado como add-on const addOns = await this.getActiveAddOns(constructoraId); return addOns.includes(moduleCode); } } ``` --- ### Contexto de Ejecución #### Contexto RLS **Definición:** Variables de sesión PostgreSQL que almacenan el contexto del request actual para aplicar RLS policies. **Variables principales:** - `app.current_constructora_id`: UUID de la constructora activa - `app.current_user_id`: UUID del usuario autenticado - `app.current_user_role`: Rol del usuario **Ciclo de vida:** 1. **Request inicia:** Middleware extrae `constructoraId` del JWT 2. **Contexto se establece:** `set_config()` antes de queries 3. **RLS se aplica:** PostgreSQL usa variables en policies 4. **Request termina:** Contexto se limpia automáticamente **Implementación:** ```typescript // Interceptor global NestJS @Injectable() export class SetRlsContextInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable { const request = context.switchToHttp().getRequest(); const { constructoraId, userId, role } = request.user; return from( this.dataSource.query(` SELECT set_config('app.current_constructora_id', $1, true), set_config('app.current_user_id', $2, true), set_config('app.current_user_role', $3, true) `, [constructoraId, userId, role]) ).pipe( switchMap(() => next.handle()) ); } } ``` **Funciones helper:** ```sql -- Obtener constructora del contexto CREATE FUNCTION get_current_constructora_id() RETURNS UUID AS $$ BEGIN RETURN current_setting('app.current_constructora_id', true)::UUID; EXCEPTION WHEN OTHERS THEN RETURN NULL; END; $$ LANGUAGE plpgsql STABLE; ``` --- ### Módulos y Features #### Módulo **Definición:** Unidad funcional del sistema que puede activarse/desactivarse por constructora según su plan. **Estructura:** ``` MAI-XXX: Módulos de Fase 1 (Alcance Inicial) MAE-XXX: Módulos de Fase 2 (Enterprise Básico) MAA-XXX: Módulos de Fase 3 (Avanzada con IA) ``` **Estados por constructora:** - **Incluido:** Módulo incluido en el plan base - **Add-on:** Disponible por pago adicional - **No disponible:** No compatible con el plan - **Activo:** Módulo contratado y funcional - **Inactivo:** Módulo no habilitado **Ejemplo:** ```yaml # Configuración de módulo MAI-007 (RRHH) modules: MAI-007: name: "RRHH, Asistencias y Nómina" plans: basic: addon # Add-on $100/mes professional: included enterprise: included dependencies: - MAI-001 # Requiere Fundamentos ``` --- #### Feature Flag **Definición:** Bandera de configuración que habilita/deshabilita funcionalidades específicas sin necesidad de deploy. **Niveles:** 1. **Global:** Afecta a toda la plataforma 2. **Por constructora:** Específico de un cliente 3. **Por módulo:** Asociado a un módulo 4. **Por usuario:** Experimental para usuarios específicos **Casos de uso:** - Gradual rollout de nuevas features - A/B testing - Habilitación de features enterprise - Deprecación controlada **Implementación:** ```typescript // Decorador para proteger endpoint con feature flag @Get('ai-insights') @RequiresFeature('ai_risk_prediction') @RequiresPlan(['enterprise']) async getAIInsights() { // Solo accesible si: // 1. Constructora tiene plan Enterprise // 2. Feature flag 'ai_risk_prediction' está habilitado } ``` --- ## 📖 Conversiones Terminológicas ### De Términos Técnicos a Negocio | Término Técnico (SaaS) | Término de Negocio (ERP) | Contexto de uso | |------------------------|--------------------------|-----------------| | Tenant | Constructora | Siempre en docs de negocio | | Tenant ID | ID de Constructora | Base de datos, API | | Multi-tenancy | Multi-empresa | Comunicación con clientes | | Tenant isolation | Aislamiento de datos por constructora | Seguridad | | Tenant admin | Administrador de constructora | Roles de usuario | | Tenant provisioning | Alta de constructora | Onboarding | | Cross-tenant access | Acceso entre constructoras | Auditoría de seguridad | | Schema-level isolation | ❌ Ya no se usa | Arquitectura legacy | | Row-level security (RLS) | Aislamiento por filas | Arquitectura actual | --- ### De Jerga de Construcción a Sistema | Término de Construcción | Término en Sistema | Tabla/Módulo | |------------------------|-------------------|--------------| | Obra | Proyecto | `projects.projects` | | Partida | Concepto de presupuesto | `budgets.budget_items` | | Estimación | Estimación de obra | `estimations.estimations` | | Cuadrilla | Cuadrilla/Equipo | `hr.crews` | | Frente de trabajo | Frente | `construction.work_fronts` | | Residente | Usuario con rol `resident` | Rol de sistema | | Avance | Avance físico/financiero | `construction.progress` | | OC (Orden de Compra) | Purchase Order | `purchases.purchase_orders` | | Requisición | Requisición de materiales | `purchases.requisitions` | | Tarjeta (asistencia) | Registro de asistencia | `hr.attendances` | --- ## 🔄 Migración de Términos Legacy ### Si encuentras estos términos, reemplaza: | ❌ Término Antiguo | ✅ Término Correcto | Razón | |-------------------|-------------------|-------| | `tenant_schema` | `constructora_id` | Ya no usamos schemas separados | | `tenant_001` | UUID único | IDs descriptivos cambiaron a UUIDs | | `search_path` | Contexto RLS (`set_config`) | Cambio de arquitectura | | `setTenantSchema()` | `setRLSContext()` | Nueva implementación | | `TenantConnectionService` | ❌ Eliminar | Ya no se necesita | | `schema: tenant_${id}` | ❌ Eliminar | Configuración legacy | --- ## 📝 Guías de Estilo ### En Código (TypeScript/SQL) **✅ Preferir:** ```typescript // Variables de dominio const constructora = await getConstructora(id); const constructoraId = user.constructoraId; // Servicios export class ConstructoraService { } // DTOs export class CreateConstructoraDto { } ``` **⚠️ Aceptable en contexto técnico:** ```typescript // Cuando el contexto SaaS es claro interface TenantConfig { } class TenantGuard implements CanActivate { } ``` **❌ Evitar:** ```typescript // Ambiguo o confuso const company = ...; // ¿Empresa cliente? ¿Empresa del sistema? const client = ...; // ¿Cliente del tenant? ¿Tenant mismo? ``` --- ### En Documentación **Documentación técnica (arquitectura, código):** - ✅ Usar "tenant" cuando se habla de patrones SaaS genéricos - ✅ Siempre aclarar: "tenant (constructora)" - ✅ Definir en primera mención **Documentación de negocio (requerimientos, casos de uso):** - ✅ Usar "constructora" exclusivamente - ❌ Evitar "tenant" - ✅ Usar términos del dominio de construcción **Documentación de usuario (manuales, FAQs):** - ✅ Usar "su empresa", "su constructora" - ❌ Nunca usar "tenant" - ✅ Lenguaje natural y cercano --- ## 🧪 Testing y Validación ### Nomenclatura de Tests **✅ Tests de RLS:** ```typescript describe('RLS Isolation - Constructora', () => { it('should prevent cross-constructora data access', () => { // ... }); it('should allow multi-constructora user to switch context', () => { // ... }); }); ``` **✅ Tests de autorización:** ```typescript describe('ConstructoraGuard', () => { it('should deny access if user does not belong to constructora', () => { // ... }); }); ``` --- ## 📚 Referencias **Documentos relacionados:** - [ARQUITECTURA-SAAS.md](./ARQUITECTURA-SAAS.md) - Arquitectura SaaS completa - [RF-AUTH-003-multi-tenancy.md](../01-fase-alcance-inicial/MAI-001-fundamentos/requerimientos/RF-AUTH-003-multi-tenancy.md) - Especificación de multi-tenancy - [TRACEABILITY.yml](../01-fase-alcance-inicial/MAI-001-fundamentos/implementacion/TRACEABILITY.yml) - Trazabilidad de implementación **Decisiones arquitectónicas:** - **2025-11-17:** Adopción de Row-Level Security (RLS) en lugar de schema-level isolation - Razón: Escalabilidad ilimitada, migraciones simples, 90% de reutilización de GAMILIT - Impacto: Cambio de `tenant_XXX` schemas a `constructora_id` discriminador --- **Generado:** 2025-11-17 **Versión:** 2.0 SaaS **Mantenedores:** @tech-lead @documentation-team