748 lines
28 KiB
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
|