16 KiB
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:
// ✅ 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 úniconombre: Nombre comercial (ej: "Constructora ABC")razon_social: Razón social legalrfc: Registro Federal de Contribuyentes (México)subdomain: Subdominio asignado (ej: "constructora-abc")plan: Plan de suscripción (Básico, Profesional, Enterprise)
Ejemplos:
-- ✅ 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
);
// ✅ En servicios de negocio
export class ConstructoraService {
async findBySubdomain(subdomain: string): Promise<Constructora> {
// ...
}
}
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:
- Cada tabla multi-tenant tiene columna
constructora_id - Se configuran RLS policies que filtran por
constructora_id - El contexto se establece por sesión:
app.current_constructora_id - PostgreSQL aplica automáticamente las políticas
Ventajas:
- ✅ Aislamiento lógico robusto
- ✅ Escalabilidad ilimitada
- ✅ Migraciones simples
- ✅ Reutilización de código GAMILIT (90%)
Ejemplo:
-- 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)
);
// 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:
-- 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):
-- 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:
director: Director general/proyectosengineer: Ingeniero de planeación/controlresident: Residente de obra/supervisorpurchases: Compras/almacénfinance: Administración/finanzashr: Recursos humanospost_sales: Postventa/garantías
Uso en RLS:
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 aauth_management.profilesconstructora_id: FK aconstructoras.constructorasrole: Rol del usuario en esta constructorais_primary: Si es la constructora principal del usuarioactive: Si la relación está activa
Casos de uso:
- Usuario trabaja en múltiples constructoras
- Cambio de rol sin perder historial
- Desactivación temporal (sin borrado)
- Usuario externo (consultor, auditor)
Ejemplo:
// 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.comviviendas-xyz.erp-construccion.comdesarrollos-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:
// 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:
- Básico ($399/mes): 10 usuarios, 6 módulos core
- Profesional ($799/mes): 25 usuarios, 12 módulos
- 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:
// Feature flag basado en plan
export class FeatureFlagsService {
async isModuleEnabled(
moduleCode: string,
constructoraId: string
): Promise<boolean> {
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 activaapp.current_user_id: UUID del usuario autenticadoapp.current_user_role: Rol del usuario
Ciclo de vida:
- Request inicia: Middleware extrae
constructoraIddel JWT - Contexto se establece:
set_config()antes de queries - RLS se aplica: PostgreSQL usa variables en policies
- Request termina: Contexto se limpia automáticamente
Implementación:
// Interceptor global NestJS
@Injectable()
export class SetRlsContextInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
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:
-- 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:
# 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:
- Global: Afecta a toda la plataforma
- Por constructora: Específico de un cliente
- Por módulo: Asociado a un módulo
- 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:
// 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:
// 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:
// Cuando el contexto SaaS es claro
interface TenantConfig { }
class TenantGuard implements CanActivate { }
❌ Evitar:
// 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:
describe('RLS Isolation - Constructora', () => {
it('should prevent cross-constructora data access', () => {
// ...
});
it('should allow multi-constructora user to switch context', () => {
// ...
});
});
✅ Tests de autorización:
describe('ConstructoraGuard', () => {
it('should deny access if user does not belong to constructora', () => {
// ...
});
});
📚 Referencias
Documentos relacionados:
- ARQUITECTURA-SAAS.md - Arquitectura SaaS completa
- RF-AUTH-003-multi-tenancy.md - Especificación de multi-tenancy
- 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_XXXschemas aconstructora_iddiscriminador
Generado: 2025-11-17 Versión: 2.0 SaaS Mantenedores: @tech-lead @documentation-team