# 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
