# TRACEABILITY-MGN-013.yaml # Matriz de Trazabilidad - MGN-013: Portal de Usuarios # Fecha: 2025-11-24 # Versión: 1.0 module: id: MGN-013 name: "Portal de Usuarios" description: "Portal de acceso para clientes y proveedores: consulta de documentos, aprobaciones, firma electrónica y mensajería" priority: P1 story_points: 29 status: Diseñado metadata: total_rf: 4 total_et_backend: 4 total_et_frontend: 4 total_tables: 4 total_tests: 80 coverage: 100% requirements: - rf_id: RF-MGN-013-001 rf_title: "Acceso Portal para Clientes" rf_file: "requerimientos-funcionales/mgn-013/RF-MGN-013-001-acceso-portal-para-clientes.md" priority: P1 story_points: 8 et_backend: file: "especificaciones-tecnicas/backend/mgn-013/ET-BACKEND-MGN-013-001-acceso-portal-para-clientes.md" endpoints: - method: POST path: /api/v1/portal/access description: "Crear acceso al portal" - method: GET path: /api/v1/portal/access description: "Listar accesos al portal" - method: GET path: /api/v1/portal/access/:id description: "Obtener acceso por ID" - method: PUT path: /api/v1/portal/access/:id description: "Actualizar acceso" - method: POST path: /api/v1/portal/access/:id/revoke description: "Revocar acceso" services: - name: "PortalAccessService" file: "src/modules/portal/services/portal-access.service.ts" methods: - create - findAll - findOne - update - revoke - validateBusinessRules controllers: - name: "PortalAccessController" file: "src/modules/portal/controllers/portal-access.controller.ts" dtos: - name: "CreatePortalAccessDto" file: "src/modules/portal/dto/create-portal-access.dto.ts" - name: "UpdatePortalAccessDto" file: "src/modules/portal/dto/update-portal-access.dto.ts" - name: "PortalAccessResponseDto" file: "src/modules/portal/dto/portal-access-response.dto.ts" - name: "RevokeAccessDto" file: "src/modules/portal/dto/revoke-access.dto.ts" et_frontend: file: "especificaciones-tecnicas/frontend/mgn-013/ET-FRONTEND-MGN-013-001-acceso-portal-para-clientes.md" routes: - path: "/portal" component: "PortalHomePage" - path: "/portal/login" component: "PortalLoginPage" - path: "/admin/portal-access" component: "PortalAccessPage" components: - name: "PortalAccessTable" file: "src/widgets/portal-access-table/ui/PortalAccessTable.tsx" type: widget - name: "CreatePortalAccessForm" file: "src/features/create-portal-access/ui/CreatePortalAccessForm.tsx" type: feature - name: "PortalAccessCard" file: "src/entities/portal-access/ui/PortalAccessCard.tsx" type: entity - name: "PortalAccessPage" file: "src/pages/portal/PortalAccessPage.tsx" type: page api_client: - name: "portalAccessApi" file: "src/entities/portal-access/api/portal-access.api.ts" methods: - getAll - getById - create - update - revoke state_management: - name: "usePortalAccessStore" file: "src/entities/portal-access/model/portal-access.store.ts" type: zustand - name: "usePortalAccess" file: "src/entities/portal-access/api/portal-access.queries.ts" type: react-query database_tables: - schema: core table: portal_users file: "database-design/schemas/core-schema-ddl.sql" operations: - SELECT - INSERT - UPDATE - DELETE (soft) indices: - idx_portal_users_tenant_id - idx_portal_users_partner_id - idx_portal_users_email rls_policy: tenant_isolation_portal_users - schema: core table: portal_access file: "database-design/schemas/core-schema-ddl.sql" operations: - SELECT - INSERT - UPDATE indices: - idx_portal_access_portal_user_id - idx_portal_access_status rls_policy: tenant_isolation_portal_access tests: backend: unit_tests: - file: "src/modules/portal/services/portal-access.service.spec.ts" test_cases: - "should create portal access for partner" - "should send invitation email" - "should validate email uniqueness" - "should revoke access successfully" - "should find all portal users for tenant" integration_tests: - file: "test/portal/portal-access.controller.e2e-spec.ts" test_cases: - "POST /api/v1/portal/access should create access" - "GET /api/v1/portal/access should return accesses" - "GET /api/v1/portal/access/:id should return access" - "PUT /api/v1/portal/access/:id should update access" - "POST /api/v1/portal/access/:id/revoke should revoke" - "should enforce tenant isolation" - "should require authentication" - "should check permissions" frontend: component_tests: - file: "src/widgets/portal-access-table/ui/PortalAccessTable.test.tsx" test_cases: - "should render table with portal users" - "should filter by status" - "should revoke access" - file: "src/features/create-portal-access/ui/CreatePortalAccessForm.test.tsx" test_cases: - "should validate required fields" - "should submit valid form" - "should show error messages" e2e_tests: - file: "e2e/portal/portal-access.spec.ts" test_cases: - "should create portal access successfully" - "should send invitation email" - "should revoke access" - "should enforce permissions" acceptance_criteria: - id: AC-001 description: "Administrador puede crear accesos al portal para partners" status: Pending test_reference: "test/portal/portal-access.controller.e2e-spec.ts:35" - id: AC-002 description: "Sistema envía email de invitación automáticamente" status: Pending test_reference: "src/modules/portal/services/portal-access.service.spec.ts:72" - id: AC-003 description: "Portal usa autenticación separada del sistema principal" status: Pending test_reference: "test/portal/portal-access.controller.e2e-spec.ts:108" business_rules: - id: RN-001 description: "Email debe ser único por tenant en portal" implementation: "database-design/schemas/core-schema-ddl.sql:CONSTRAINT uq_portal_users_email_tenant" test_reference: "src/modules/portal/services/portal-access.service.spec.ts:95" - id: RN-002 description: "Portal user debe estar vinculado a un partner" implementation: "src/modules/portal/services/portal-access.service.ts:create()" test_reference: "src/modules/portal/services/portal-access.service.spec.ts:122" - id: RN-003 description: "Acceso revocado no permite login en portal" implementation: "src/modules/portal/services/portal-access.service.ts:validateAccess()" test_reference: "src/modules/portal/services/portal-access.service.spec.ts:148" dependencies: rf_dependencies: - RF-MGN-001-001 - RF-MGN-003-001 module_dependencies: - MGN-001 - MGN-003 - MGN-014 external_dependencies: - name: "@nestjs/passport" version: "^10.0.0" - name: "nodemailer" version: "^6.9.0" - rf_id: RF-MGN-013-002 rf_title: "Vista de Documentos en Portal" rf_file: "requerimientos-funcionales/mgn-013/RF-MGN-013-002-vista-de-documentos-en-portal.md" priority: P1 story_points: 8 et_backend: file: "especificaciones-tecnicas/backend/mgn-013/ET-BACKEND-MGN-013-002-vista-de-documentos-en-portal.md" endpoints: - method: GET path: /api/v1/portal/documents description: "Listar documentos accesibles" - method: GET path: /api/v1/portal/documents/:id description: "Obtener documento por ID" - method: GET path: /api/v1/portal/documents/:id/download description: "Descargar documento" - method: POST path: /api/v1/portal/documents/:id/share description: "Compartir documento" services: - name: "PortalDocumentService" file: "src/modules/portal/services/portal-document.service.ts" methods: - findAll - findOne - download - share - validateBusinessRules controllers: - name: "PortalDocumentController" file: "src/modules/portal/controllers/portal-document.controller.ts" dtos: - name: "PortalDocumentResponseDto" file: "src/modules/portal/dto/portal-document-response.dto.ts" - name: "ShareDocumentDto" file: "src/modules/portal/dto/share-document.dto.ts" - name: "FilterPortalDocumentDto" file: "src/modules/portal/dto/filter-portal-document.dto.ts" et_frontend: file: "especificaciones-tecnicas/frontend/mgn-013/ET-FRONTEND-MGN-013-002-vista-de-documentos-en-portal.md" routes: - path: "/portal/documents" component: "PortalDocumentsPage" - path: "/portal/documents/:id" component: "ViewPortalDocumentPage" components: - name: "PortalDocumentsTable" file: "src/widgets/portal-documents-table/ui/PortalDocumentsTable.tsx" type: widget - name: "DocumentViewer" file: "src/features/document-viewer/ui/DocumentViewer.tsx" type: feature - name: "PortalDocumentCard" file: "src/entities/portal-document/ui/PortalDocumentCard.tsx" type: entity - name: "PortalDocumentsPage" file: "src/pages/portal/PortalDocumentsPage.tsx" type: page api_client: - name: "portalDocumentApi" file: "src/entities/portal-document/api/portal-document.api.ts" methods: - getAll - getById - download - share state_management: - name: "usePortalDocumentStore" file: "src/entities/portal-document/model/portal-document.store.ts" type: zustand - name: "usePortalDocuments" file: "src/entities/portal-document/api/portal-document.queries.ts" type: react-query database_tables: - schema: core table: portal_documents file: "database-design/schemas/core-schema-ddl.sql" operations: - SELECT indices: - idx_portal_documents_tenant_id - idx_portal_documents_partner_id - idx_portal_documents_type rls_policy: tenant_isolation_portal_documents tests: backend: unit_tests: - file: "src/modules/portal/services/portal-document.service.spec.ts" test_cases: - "should list documents for portal user" - "should filter by document type" - "should download document securely" - "should validate partner access" - "should share document with other portal users" integration_tests: - file: "test/portal/portal-document.controller.e2e-spec.ts" test_cases: - "GET /api/v1/portal/documents should return documents" - "GET /api/v1/portal/documents/:id should return document" - "GET /api/v1/portal/documents/:id/download should download" - "POST /api/v1/portal/documents/:id/share should share" - "should enforce partner isolation" - "should require portal authentication" - "should check document permissions" frontend: component_tests: - file: "src/widgets/portal-documents-table/ui/PortalDocumentsTable.test.tsx" test_cases: - "should render table with documents" - "should filter by type" - "should download document" - file: "src/features/document-viewer/ui/DocumentViewer.test.tsx" test_cases: - "should render document preview" - "should support PDF viewer" - "should show download button" e2e_tests: - file: "e2e/portal/portal-documents.spec.ts" test_cases: - "should list available documents" - "should view document details" - "should download document" - "should enforce access permissions" acceptance_criteria: - id: AC-001 description: "Cliente puede ver documentos compartidos con él (facturas, cotizaciones)" status: Pending test_reference: "test/portal/portal-document.controller.e2e-spec.ts:42" - id: AC-002 description: "Sistema filtra documentos por partner automáticamente" status: Pending test_reference: "src/modules/portal/services/portal-document.service.spec.ts:78" - id: AC-003 description: "Documentos se pueden descargar en PDF" status: Pending test_reference: "e2e/portal/portal-documents.spec.ts:115" business_rules: - id: RN-001 description: "Portal user solo ve documentos de su partner" implementation: "src/modules/portal/services/portal-document.service.ts:findAll()" test_reference: "src/modules/portal/services/portal-document.service.spec.ts:102" - id: RN-002 description: "Documentos soportados: invoices, quotations, purchase_orders" implementation: "database-design/schemas/core-schema-ddl.sql:portal_documents.document_type" test_reference: "src/modules/portal/services/portal-document.service.spec.ts:128" - id: RN-003 description: "Descargas se registran en audit log" implementation: "src/modules/portal/services/portal-document.service.ts:download()" test_reference: "src/modules/portal/services/portal-document.service.spec.ts:155" dependencies: rf_dependencies: - RF-MGN-013-001 - RF-MGN-004-005 - RF-MGN-007-001 module_dependencies: - MGN-001 - MGN-004 - MGN-007 external_dependencies: - name: "puppeteer" version: "^21.0.0" - rf_id: RF-MGN-013-003 rf_title: "Aprobación y Firma Electrónica" rf_file: "requerimientos-funcionales/mgn-013/RF-MGN-013-003-aprobación-y-firma-electrónica.md" priority: P1 story_points: 8 et_backend: file: "especificaciones-tecnicas/backend/mgn-013/ET-BACKEND-MGN-013-003-aprobación-y-firma-electrónica.md" endpoints: - method: GET path: /api/v1/portal/approvals description: "Listar documentos pendientes de aprobación" - method: POST path: /api/v1/portal/approvals/:id/approve description: "Aprobar documento" - method: POST path: /api/v1/portal/approvals/:id/reject description: "Rechazar documento" - method: POST path: /api/v1/portal/approvals/:id/sign description: "Firmar documento electrónicamente" services: - name: "PortalApprovalService" file: "src/modules/portal/services/portal-approval.service.ts" methods: - findPending - approve - reject - sign - validateBusinessRules controllers: - name: "PortalApprovalController" file: "src/modules/portal/controllers/portal-approval.controller.ts" dtos: - name: "ApproveDocumentDto" file: "src/modules/portal/dto/approve-document.dto.ts" - name: "RejectDocumentDto" file: "src/modules/portal/dto/reject-document.dto.ts" - name: "SignDocumentDto" file: "src/modules/portal/dto/sign-document.dto.ts" - name: "ApprovalResponseDto" file: "src/modules/portal/dto/approval-response.dto.ts" et_frontend: file: "especificaciones-tecnicas/frontend/mgn-013/ET-FRONTEND-MGN-013-003-aprobación-y-firma-electrónica.md" routes: - path: "/portal/approvals" component: "PortalApprovalsPage" - path: "/portal/approvals/:id" component: "ViewApprovalPage" components: - name: "ApprovalsTable" file: "src/widgets/approvals-table/ui/ApprovalsTable.tsx" type: widget - name: "SignatureDialog" file: "src/features/signature-dialog/ui/SignatureDialog.tsx" type: feature - name: "ApprovalCard" file: "src/entities/approval/ui/ApprovalCard.tsx" type: entity - name: "ApprovalsPage" file: "src/pages/portal/ApprovalsPage.tsx" type: page api_client: - name: "portalApprovalApi" file: "src/entities/portal-approval/api/portal-approval.api.ts" methods: - getPending - approve - reject - sign state_management: - name: "usePortalApprovalStore" file: "src/entities/portal-approval/model/portal-approval.store.ts" type: zustand - name: "usePortalApprovals" file: "src/entities/portal-approval/api/portal-approval.queries.ts" type: react-query database_tables: - schema: core table: portal_documents file: "database-design/schemas/core-schema-ddl.sql" operations: - SELECT - UPDATE indices: - idx_portal_documents_approval_status rls_policy: tenant_isolation_portal_documents tests: backend: unit_tests: - file: "src/modules/portal/services/portal-approval.service.spec.ts" test_cases: - "should list pending approvals" - "should approve document successfully" - "should reject document with reason" - "should sign document electronically" - "should validate signature" integration_tests: - file: "test/portal/portal-approval.controller.e2e-spec.ts" test_cases: - "GET /api/v1/portal/approvals should return pending" - "POST /api/v1/portal/approvals/:id/approve should approve" - "POST /api/v1/portal/approvals/:id/reject should reject" - "POST /api/v1/portal/approvals/:id/sign should sign" - "should enforce partner isolation" - "should require portal authentication" - "should validate approval permissions" frontend: component_tests: - file: "src/widgets/approvals-table/ui/ApprovalsTable.test.tsx" test_cases: - "should render pending approvals" - "should approve document" - "should reject document" - file: "src/features/signature-dialog/ui/SignatureDialog.test.tsx" test_cases: - "should capture signature" - "should validate signature" - "should submit signed document" e2e_tests: - file: "e2e/portal/approvals.spec.ts" test_cases: - "should approve document successfully" - "should reject document with reason" - "should sign document electronically" - "should enforce permissions" acceptance_criteria: - id: AC-001 description: "Cliente puede aprobar o rechazar documentos desde portal" status: Pending test_reference: "test/portal/portal-approval.controller.e2e-spec.ts:45" - id: AC-002 description: "Sistema soporta firma electrónica con captura de firma" status: Pending test_reference: "src/modules/portal/services/portal-approval.service.spec.ts:82" - id: AC-003 description: "Aprobaciones y firmas se registran en audit log" status: Pending test_reference: "e2e/portal/approvals.spec.ts:118" business_rules: - id: RN-001 description: "Solo documentos en estado 'pending_approval' pueden aprobarse" implementation: "src/modules/portal/services/portal-approval.service.ts:validate()" test_reference: "src/modules/portal/services/portal-approval.service.spec.ts:105" - id: RN-002 description: "Firma electrónica incluye timestamp y IP del firmante" implementation: "src/modules/portal/services/portal-approval.service.ts:sign()" test_reference: "src/modules/portal/services/portal-approval.service.spec.ts:132" - id: RN-003 description: "Documento rechazado requiere razón obligatoria" implementation: "src/modules/portal/dto/reject-document.dto.ts:@IsNotEmpty()" test_reference: "src/modules/portal/services/portal-approval.service.spec.ts:158" dependencies: rf_dependencies: - RF-MGN-013-002 module_dependencies: - MGN-001 - MGN-014 external_dependencies: - name: "signature_pad" version: "^4.1.0" - rf_id: RF-MGN-013-004 rf_title: "Mensajería en Portal" rf_file: "requerimientos-funcionales/mgn-013/RF-MGN-013-004-mensajería-en-portal.md" priority: P1 story_points: 5 et_backend: file: "especificaciones-tecnicas/backend/mgn-013/ET-BACKEND-MGN-013-004-mensajería-en-portal.md" endpoints: - method: POST path: /api/v1/portal/messages description: "Enviar mensaje" - method: GET path: /api/v1/portal/messages description: "Listar mensajes" - method: GET path: /api/v1/portal/messages/:id description: "Obtener mensaje por ID" - method: POST path: /api/v1/portal/messages/:id/reply description: "Responder mensaje" - method: POST path: /api/v1/portal/messages/:id/mark-read description: "Marcar como leído" services: - name: "PortalMessageService" file: "src/modules/portal/services/portal-message.service.ts" methods: - create - findAll - findOne - reply - markRead - validateBusinessRules controllers: - name: "PortalMessageController" file: "src/modules/portal/controllers/portal-message.controller.ts" dtos: - name: "CreatePortalMessageDto" file: "src/modules/portal/dto/create-portal-message.dto.ts" - name: "ReplyMessageDto" file: "src/modules/portal/dto/reply-message.dto.ts" - name: "PortalMessageResponseDto" file: "src/modules/portal/dto/portal-message-response.dto.ts" - name: "FilterPortalMessageDto" file: "src/modules/portal/dto/filter-portal-message.dto.ts" et_frontend: file: "especificaciones-tecnicas/frontend/mgn-013/ET-FRONTEND-MGN-013-004-mensajería-en-portal.md" routes: - path: "/portal/messages" component: "PortalMessagesPage" - path: "/portal/messages/:id" component: "ViewPortalMessagePage" components: - name: "PortalMessagesTable" file: "src/widgets/portal-messages-table/ui/PortalMessagesTable.tsx" type: widget - name: "ComposeMessageForm" file: "src/features/compose-message/ui/ComposeMessageForm.tsx" type: feature - name: "PortalMessageCard" file: "src/entities/portal-message/ui/PortalMessageCard.tsx" type: entity - name: "PortalMessagesPage" file: "src/pages/portal/PortalMessagesPage.tsx" type: page api_client: - name: "portalMessageApi" file: "src/entities/portal-message/api/portal-message.api.ts" methods: - getAll - getById - create - reply - markRead state_management: - name: "usePortalMessageStore" file: "src/entities/portal-message/model/portal-message.store.ts" type: zustand - name: "usePortalMessages" file: "src/entities/portal-message/api/portal-message.queries.ts" type: react-query database_tables: - schema: core table: portal_messages file: "database-design/schemas/core-schema-ddl.sql" operations: - SELECT - INSERT - UPDATE indices: - idx_portal_messages_tenant_id - idx_portal_messages_sender_id - idx_portal_messages_recipient_id - idx_portal_messages_created_at rls_policy: tenant_isolation_portal_messages tests: backend: unit_tests: - file: "src/modules/portal/services/portal-message.service.spec.ts" test_cases: - "should send message successfully" - "should list messages for portal user" - "should reply to message" - "should mark message as read" - "should send notification on new message" integration_tests: - file: "test/portal/portal-message.controller.e2e-spec.ts" test_cases: - "POST /api/v1/portal/messages should send message" - "GET /api/v1/portal/messages should return messages" - "GET /api/v1/portal/messages/:id should return message" - "POST /api/v1/portal/messages/:id/reply should reply" - "POST /api/v1/portal/messages/:id/mark-read should mark read" - "should enforce partner isolation" - "should require portal authentication" frontend: component_tests: - file: "src/widgets/portal-messages-table/ui/PortalMessagesTable.test.tsx" test_cases: - "should render messages" - "should filter by read/unread" - "should mark as read" - file: "src/features/compose-message/ui/ComposeMessageForm.test.tsx" test_cases: - "should validate required fields" - "should submit message" - "should attach files" e2e_tests: - file: "e2e/portal/messages.spec.ts" test_cases: - "should send message successfully" - "should reply to message" - "should receive notifications" - "should enforce permissions" acceptance_criteria: - id: AC-001 description: "Cliente puede enviar y recibir mensajes con la empresa" status: Pending test_reference: "test/portal/portal-message.controller.e2e-spec.ts:48" - id: AC-002 description: "Sistema envía notificaciones de nuevos mensajes" status: Pending test_reference: "src/modules/portal/services/portal-message.service.spec.ts:85" - id: AC-003 description: "Mensajes soportan adjuntos de archivos" status: Pending test_reference: "e2e/portal/messages.spec.ts:122" business_rules: - id: RN-001 description: "Portal user solo ve mensajes enviados o recibidos por él" implementation: "src/modules/portal/services/portal-message.service.ts:findAll()" test_reference: "src/modules/portal/services/portal-message.service.spec.ts:108" - id: RN-002 description: "Nuevo mensaje genera notificación email" implementation: "src/modules/portal/services/portal-message.service.ts:create()" test_reference: "src/modules/portal/services/portal-message.service.spec.ts:135" - id: RN-003 description: "Mensajes soportan hilos de conversación (parent_id)" implementation: "database-design/schemas/core-schema-ddl.sql:portal_messages.parent_id" test_reference: "src/modules/portal/services/portal-message.service.spec.ts:162" dependencies: rf_dependencies: - RF-MGN-013-001 - RF-MGN-014-002 module_dependencies: - MGN-001 - MGN-014 external_dependencies: - name: "nodemailer" version: "^6.9.0" coverage: rf_to_et_backend: 100% rf_to_et_frontend: 100% rf_to_database: 100% rf_to_tests: 100% backend_tests: 100% frontend_tests: 100% statistics: total_endpoints: 20 total_components: 16 total_tables: 4 total_test_cases: 80 estimated_duration_sprints: 2