erp-core/docs/04-modelado/trazabilidad/TRACEABILITY-MGN-013.yaml

748 lines
28 KiB
YAML

# 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