# TRACEABILITY.yml - MGN-004: Multi-tenant # Matriz de trazabilidad: Documentacion -> Codigo # Ubicacion: docs/01-fase-foundation/MGN-004-tenants/implementacion/ epic_code: MGN-004 epic_name: Multi-tenant phase: 1 phase_name: Foundation story_points: 35 status: documented # ============================================================================= # DOCUMENTACION # ============================================================================= documentation: requirements: - id: RF-TENANT-001 file: ../requerimientos/RF-TENANT-001.md title: CRUD de Tenants priority: P0 status: migrated description: | Crear, leer, actualizar y eliminar tenants (organizaciones). Cada tenant tiene nombre, slug, dominio y configuracion. - id: RF-TENANT-002 file: ../requerimientos/RF-TENANT-002.md title: Configuracion por Tenant priority: P0 status: migrated description: | Configuracion personalizada: moneda, zona horaria, formato de fecha, configuracion fiscal, modulos habilitados. - id: RF-TENANT-003 file: ../requerimientos/RF-TENANT-003.md title: Planes y Suscripciones priority: P1 status: migrated description: | Definicion de planes con features y limites. Suscripciones de tenants a planes. - id: RF-TENANT-004 file: ../requerimientos/RF-TENANT-004.md title: Aislamiento RLS priority: P0 status: migrated description: | Row Level Security en PostgreSQL para aislar datos. Contexto de tenant en cada request. requirements_index: file: ../requerimientos/INDICE-RF-TENANT.md status: migrated specifications: - id: ET-TENANTS-001 file: ../especificaciones/ET-tenants-backend.md title: Backend Tenants rf: [RF-TENANT-001, RF-TENANT-002, RF-TENANT-003, RF-TENANT-004] status: migrated - id: ET-TENANTS-002 file: ../especificaciones/ET-TENANT-database.md title: Database Tenants rf: [RF-TENANT-001, RF-TENANT-002, RF-TENANT-003, RF-TENANT-004] status: migrated user_stories: - id: US-MGN004-001 file: ../historias-usuario/US-MGN004-001.md title: Crear Tenant rf: [RF-TENANT-001] story_points: 8 status: migrated - id: US-MGN004-002 file: ../historias-usuario/US-MGN004-002.md title: Configurar Tenant rf: [RF-TENANT-002] story_points: 5 status: migrated - id: US-MGN004-003 file: ../historias-usuario/US-MGN004-003.md title: Gestionar Suscripcion rf: [RF-TENANT-003] story_points: 8 status: migrated - id: US-MGN004-004 file: ../historias-usuario/US-MGN004-004.md title: Cambiar Plan rf: [RF-TENANT-003] story_points: 5 status: migrated backlog: file: ../historias-usuario/BACKLOG-MGN004.md status: migrated # ============================================================================= # IMPLEMENTACION # ============================================================================= implementation: database: schema: core_tenants path: apps/database/ddl/schemas/core_tenants/ tables: - name: tenants file: apps/database/ddl/schemas/core_tenants/tables/tenants.sql rf: RF-TENANTS-001 status: pending columns: - {name: id, type: UUID, pk: true} - {name: name, type: VARCHAR(200)} - {name: slug, type: VARCHAR(100), unique: true} - {name: domain, type: VARCHAR(255)} - {name: logo_url, type: TEXT} - {name: settings, type: JSONB} - {name: is_active, type: BOOLEAN} - {name: trial_ends_at, type: TIMESTAMPTZ} - {name: created_at, type: TIMESTAMPTZ} - {name: updated_at, type: TIMESTAMPTZ} - {name: deleted_at, type: TIMESTAMPTZ} indexes: - idx_tenants_slug - idx_tenants_domain - name: tenant_settings file: apps/database/ddl/schemas/core_tenants/tables/tenant_settings.sql rf: RF-TENANTS-002 status: pending columns: - {name: id, type: UUID, pk: true} - {name: tenant_id, type: UUID, fk: tenants, unique: true} - {name: currency, type: VARCHAR(3)} - {name: timezone, type: VARCHAR(50)} - {name: date_format, type: VARCHAR(20)} - {name: fiscal_config, type: JSONB} - {name: modules_enabled, type: JSONB} - {name: created_at, type: TIMESTAMPTZ} - {name: updated_at, type: TIMESTAMPTZ} - name: plans file: apps/database/ddl/schemas/core_tenants/tables/plans.sql rf: RF-TENANTS-003 status: pending columns: - {name: id, type: UUID, pk: true} - {name: name, type: VARCHAR(100)} - {name: slug, type: VARCHAR(50), unique: true} - {name: description, type: TEXT} - {name: price_monthly, type: DECIMAL(10,2)} - {name: price_yearly, type: DECIMAL(10,2)} - {name: features, type: JSONB} - {name: limits, type: JSONB} - {name: is_active, type: BOOLEAN} - {name: created_at, type: TIMESTAMPTZ} - name: subscriptions file: apps/database/ddl/schemas/core_tenants/tables/subscriptions.sql rf: RF-TENANTS-003 status: pending columns: - {name: id, type: UUID, pk: true} - {name: tenant_id, type: UUID, fk: tenants} - {name: plan_id, type: UUID, fk: plans} - {name: status, type: VARCHAR(20)} - {name: starts_at, type: TIMESTAMPTZ} - {name: ends_at, type: TIMESTAMPTZ} - {name: canceled_at, type: TIMESTAMPTZ} - {name: created_at, type: TIMESTAMPTZ} indexes: - idx_subscriptions_tenant - idx_subscriptions_status rls_policies: - name: tenant_isolation description: Politica base para aislamiento de datos rf: RF-TENANTS-004 status: pending tables: ALL definition: | CREATE POLICY tenant_isolation ON {table} USING (tenant_id = current_setting('app.current_tenant_id')::uuid); functions: - name: set_tenant_context file: apps/database/ddl/schemas/core_tenants/functions/set_tenant_context.sql rf: RF-TENANTS-004 status: pending params: [p_tenant_id UUID] returns: VOID description: Establece contexto de tenant para RLS - name: create_tenant_with_defaults file: apps/database/ddl/schemas/core_tenants/functions/create_tenant_with_defaults.sql rf: RF-TENANTS-005 status: pending params: [p_name VARCHAR, p_slug VARCHAR, p_admin_user_id UUID] returns: UUID description: Crea tenant con datos iniciales y roles backend: module: tenants path: apps/backend/src/modules/tenants/ framework: NestJS entities: - name: Tenant file: apps/backend/src/modules/tenants/entities/tenant.entity.ts rf: RF-TENANTS-001 status: pending - name: TenantSettings file: apps/backend/src/modules/tenants/entities/tenant-settings.entity.ts rf: RF-TENANTS-002 status: pending - name: Plan file: apps/backend/src/modules/tenants/entities/plan.entity.ts rf: RF-TENANTS-003 status: pending - name: Subscription file: apps/backend/src/modules/tenants/entities/subscription.entity.ts rf: RF-TENANTS-003 status: pending services: - name: TenantsService file: apps/backend/src/modules/tenants/tenants.service.ts rf: [RF-TENANTS-001, RF-TENANTS-005] status: pending methods: - {name: create, rf: RF-TENANTS-001} - {name: findAll, rf: RF-TENANTS-001} - {name: findOne, rf: RF-TENANTS-001} - {name: findBySlug, rf: RF-TENANTS-001} - {name: update, rf: RF-TENANTS-001} - {name: remove, rf: RF-TENANTS-001} - {name: onboard, rf: RF-TENANTS-005} - name: TenantSettingsService file: apps/backend/src/modules/tenants/tenant-settings.service.ts rf: [RF-TENANTS-002] status: pending methods: - {name: getSettings, rf: RF-TENANTS-002} - {name: updateSettings, rf: RF-TENANTS-002} - name: PlansService file: apps/backend/src/modules/tenants/plans.service.ts rf: [RF-TENANTS-003] status: pending methods: - {name: findAll, rf: RF-TENANTS-003} - {name: findOne, rf: RF-TENANTS-003} - name: SubscriptionsService file: apps/backend/src/modules/tenants/subscriptions.service.ts rf: [RF-TENANTS-003] status: pending methods: - {name: create, rf: RF-TENANTS-003} - {name: getCurrentSubscription, rf: RF-TENANTS-003} - {name: changePlan, rf: RF-TENANTS-003} - {name: cancel, rf: RF-TENANTS-003} middleware: - name: TenantMiddleware file: apps/backend/src/modules/tenants/middleware/tenant.middleware.ts rf: RF-TENANTS-004 status: pending description: | Extrae tenant_id del JWT y establece contexto. SET LOCAL app.current_tenant_id = '{uuid}' guards: - name: TenantGuard file: apps/backend/src/modules/tenants/guards/tenant.guard.ts rf: RF-TENANTS-004 status: pending description: Verifica que tenant este activo - name: SubscriptionGuard file: apps/backend/src/modules/tenants/guards/subscription.guard.ts rf: RF-TENANTS-003 status: pending description: Verifica que suscripcion este activa controllers: - name: TenantsController file: apps/backend/src/modules/tenants/tenants.controller.ts status: pending endpoints: - method: GET path: /api/v1/tenants rf: RF-TENANTS-001 description: Listar tenants (super admin) - method: POST path: /api/v1/tenants rf: RF-TENANTS-001 description: Crear tenant - method: GET path: /api/v1/tenants/:id rf: RF-TENANTS-001 description: Obtener tenant - method: PATCH path: /api/v1/tenants/:id rf: RF-TENANTS-001 description: Actualizar tenant - method: GET path: /api/v1/tenants/current rf: RF-TENANTS-001 description: Tenant actual del usuario - method: GET path: /api/v1/tenants/current/settings rf: RF-TENANTS-002 description: Configuracion del tenant - method: PATCH path: /api/v1/tenants/current/settings rf: RF-TENANTS-002 description: Actualizar configuracion - name: PlansController file: apps/backend/src/modules/tenants/plans.controller.ts status: pending endpoints: - method: GET path: /api/v1/plans rf: RF-TENANTS-003 description: Listar planes disponibles - method: GET path: /api/v1/plans/:id rf: RF-TENANTS-003 description: Detalle de plan - name: SubscriptionsController file: apps/backend/src/modules/tenants/subscriptions.controller.ts status: pending endpoints: - method: GET path: /api/v1/subscriptions/current rf: RF-TENANTS-003 description: Suscripcion actual - method: POST path: /api/v1/subscriptions rf: RF-TENANTS-003 description: Crear suscripcion - method: PATCH path: /api/v1/subscriptions/current/plan rf: RF-TENANTS-003 description: Cambiar plan - method: POST path: /api/v1/subscriptions/current/cancel rf: RF-TENANTS-003 description: Cancelar suscripcion decorators: - name: CurrentTenant file: apps/backend/src/modules/tenants/decorators/current-tenant.decorator.ts rf: RF-TENANTS-004 status: pending description: Inyecta tenant actual del request frontend: feature: tenants path: apps/frontend/src/features/tenants/ framework: React pages: - name: TenantsPage file: apps/frontend/src/features/tenants/pages/TenantsPage.tsx rf: RF-TENANTS-001 status: pending route: /admin/tenants description: Lista de tenants (super admin) - name: TenantSettingsPage file: apps/frontend/src/features/tenants/pages/TenantSettingsPage.tsx rf: RF-TENANTS-002 status: pending route: /settings/organization - name: OnboardingPage file: apps/frontend/src/features/tenants/pages/OnboardingPage.tsx rf: RF-TENANTS-005 status: pending route: /onboarding - name: PlansPage file: apps/frontend/src/features/tenants/pages/PlansPage.tsx rf: RF-TENANTS-003 status: pending route: /settings/subscription components: - name: TenantSettingsForm file: apps/frontend/src/features/tenants/components/TenantSettingsForm.tsx rf: RF-TENANTS-002 status: pending - name: OnboardingWizard file: apps/frontend/src/features/tenants/components/OnboardingWizard.tsx rf: RF-TENANTS-005 status: pending - name: PlanCard file: apps/frontend/src/features/tenants/components/PlanCard.tsx rf: RF-TENANTS-003 status: pending - name: SubscriptionStatus file: apps/frontend/src/features/tenants/components/SubscriptionStatus.tsx rf: RF-TENANTS-003 status: pending stores: - name: tenantStore file: apps/frontend/src/features/tenants/stores/tenantStore.ts rf: [RF-TENANTS-001, RF-TENANTS-002] status: pending state: - {name: currentTenant, type: "Tenant | null"} - {name: settings, type: "TenantSettings | null"} - {name: subscription, type: "Subscription | null"} actions: - fetchCurrentTenant - updateSettings - changePlan hooks: - name: useTenant file: apps/frontend/src/features/tenants/hooks/useTenant.ts rf: RF-TENANTS-001 status: pending description: Hook para acceder a tenant actual - name: useSubscription file: apps/frontend/src/features/tenants/hooks/useSubscription.ts rf: RF-TENANTS-003 status: pending description: Hook para verificar features del plan # ============================================================================= # DEPENDENCIAS # ============================================================================= dependencies: depends_on: - module: MGN-001 type: hard reason: Tenant ID se incluye en JWT - module: MGN-002 type: hard reason: Usuarios pertenecen a tenants - module: MGN-003 type: hard reason: Roles son por tenant required_by: - module: MGN-005 type: hard reason: Catalogos son por tenant - module: MGN-006 type: hard reason: Inventario por tenant - module: MGN-007 type: hard reason: Ventas por tenant - module: ALL_BUSINESS type: hard reason: Todos los datos son por tenant # ============================================================================= # TESTS # ============================================================================= tests: unit: - name: TenantsService.spec.ts file: apps/backend/src/modules/tenants/__tests__/tenants.service.spec.ts status: pending cases: 12 - name: SubscriptionsService.spec.ts file: apps/backend/src/modules/tenants/__tests__/subscriptions.service.spec.ts status: pending cases: 8 - name: TenantMiddleware.spec.ts file: apps/backend/src/modules/tenants/__tests__/tenant.middleware.spec.ts status: pending cases: 6 integration: - name: tenants.controller.e2e.spec.ts file: apps/backend/test/tenants/tenants.controller.e2e.spec.ts status: pending cases: 15 - name: rls-isolation.e2e.spec.ts file: apps/backend/test/tenants/rls-isolation.e2e.spec.ts status: pending cases: 10 description: Tests de aislamiento RLS entre tenants coverage: target: 80% current: 0% # ============================================================================= # METRICAS # ============================================================================= metrics: story_points: estimated: 35 actual: null files: database: 8 backend: 20 frontend: 12 tests: 8 total: 48 # ============================================================================= # HISTORIAL # ============================================================================= history: - date: "2025-12-05" action: "Creacion de TRACEABILITY.yml" author: Requirements-Analyst