# TRACEABILITY-MGN-014.yaml
# Matriz de Trazabilidad - MGN-014: Mensajería y Notificaciones
# Fecha: 2025-11-24
# Versión: 1.0

module:
  id: MGN-014
  name: "Mensajería y Notificaciones"
  description: "Sistema Chatter (mail.thread), notificaciones in-app y email, tracking de cambios, actividades, followers y templates"
  priority: P0
  story_points: 47
  status: Diseñado

metadata:
  total_rf: 6
  total_et_backend: 6
  total_et_frontend: 6
  total_tables: 6
  total_tests: 120
  coverage: 100%

requirements:
  - rf_id: RF-MGN-014-001
    rf_title: "Sistema de Mensajes (Chatter)"
    rf_file: "requerimientos-funcionales/mgn-014/RF-MGN-014-001-sistema-de-mensajes-chatter.md"
    priority: P0
    story_points: 13

    et_backend:
      file: "especificaciones-tecnicas/backend/mgn-014/ET-BACKEND-MGN-014-001-sistema-de-mensajes-chatter.md"
      endpoints:
        - method: POST
          path: /api/v1/messages
          description: "Publicar mensaje"
        - method: GET
          path: /api/v1/messages
          description: "Listar mensajes"
        - method: GET
          path: /api/v1/messages/:id
          description: "Obtener mensaje por ID"
        - method: DELETE
          path: /api/v1/messages/:id
          description: "Eliminar mensaje"
        - method: POST
          path: /api/v1/messages/:id/reply
          description: "Responder mensaje"
      services:
        - name: "MessageService"
          file: "src/modules/messaging/services/message.service.ts"
          methods:
            - create
            - findAll
            - findOne
            - remove
            - reply
            - validateBusinessRules
      controllers:
        - name: "MessageController"
          file: "src/modules/messaging/controllers/message.controller.ts"
      dtos:
        - name: "CreateMessageDto"
          file: "src/modules/messaging/dto/create-message.dto.ts"
        - name: "MessageResponseDto"
          file: "src/modules/messaging/dto/message-response.dto.ts"
        - name: "FilterMessageDto"
          file: "src/modules/messaging/dto/filter-message.dto.ts"
        - name: "ReplyMessageDto"
          file: "src/modules/messaging/dto/reply-message.dto.ts"

    et_frontend:
      file: "especificaciones-tecnicas/frontend/mgn-014/ET-FRONTEND-MGN-014-001-sistema-de-mensajes-chatter.md"
      routes:
        - path: "/messages"
          component: "MessagesPage"
        - path: "/*/:modelId/chatter"
          component: "ChatterWidget"
      components:
        - name: "ChatterWidget"
          file: "src/widgets/chatter-widget/ui/ChatterWidget.tsx"
          type: widget
        - name: "MessageComposer"
          file: "src/features/message-composer/ui/MessageComposer.tsx"
          type: feature
        - name: "MessageCard"
          file: "src/entities/message/ui/MessageCard.tsx"
          type: entity
        - name: "MessagesPage"
          file: "src/pages/messaging/MessagesPage.tsx"
          type: page
      api_client:
        - name: "messageApi"
          file: "src/entities/message/api/message.api.ts"
          methods:
            - getAll
            - getById
            - create
            - reply
            - delete
      state_management:
        - name: "useMessageStore"
          file: "src/entities/message/model/message.store.ts"
          type: zustand
        - name: "useMessages"
          file: "src/entities/message/api/message.queries.ts"
          type: react-query

    database_tables:
      - schema: system
        table: messages
        file: "database-design/schemas/system-schema-ddl.sql"
        operations:
          - SELECT
          - INSERT
          - UPDATE
          - DELETE
        indices:
          - idx_messages_tenant_id
          - idx_messages_res_model_res_id
          - idx_messages_author_id
          - idx_messages_message_type
        rls_policy: tenant_isolation_messages
      - schema: system
        table: attachments
        file: "database-design/schemas/system-schema-ddl.sql"
        operations:
          - SELECT
          - INSERT
          - DELETE
        indices:
          - idx_attachments_message_id
          - idx_attachments_res_model_res_id
        rls_policy: tenant_isolation_attachments

    tests:
      backend:
        unit_tests:
          - file: "src/modules/messaging/services/message.service.spec.ts"
            test_cases:
              - "should post message to any model"
              - "should support message types (comment, notification, email)"
              - "should attach files to message"
              - "should reply to message"
              - "should notify followers on new message"
        integration_tests:
          - file: "test/messaging/message.controller.e2e-spec.ts"
            test_cases:
              - "POST /api/v1/messages should post message"
              - "GET /api/v1/messages should return messages"
              - "GET /api/v1/messages/:id should return message"
              - "DELETE /api/v1/messages/:id should delete message"
              - "POST /api/v1/messages/:id/reply should reply"
              - "should enforce tenant isolation"
              - "should require authentication"
              - "should check permissions"
      frontend:
        component_tests:
          - file: "src/widgets/chatter-widget/ui/ChatterWidget.test.tsx"
            test_cases:
              - "should render message thread"
              - "should post new message"
              - "should reply to message"
          - file: "src/features/message-composer/ui/MessageComposer.test.tsx"
            test_cases:
              - "should compose message"
              - "should attach files"
              - "should mention users"
        e2e_tests:
          - file: "e2e/messaging/chatter.spec.ts"
            test_cases:
              - "should post message successfully"
              - "should reply to message"
              - "should attach files"
              - "should enforce permissions"

    acceptance_criteria:
      - id: AC-001
        description: "Usuario puede publicar mensajes en cualquier registro (mail.thread)"
        status: Pending
        test_reference: "test/messaging/message.controller.e2e-spec.ts:35"
      - id: AC-002
        description: "Mensajes soportan adjuntos de archivos"
        status: Pending
        test_reference: "src/modules/messaging/services/message.service.spec.ts:72"
      - id: AC-003
        description: "Sistema notifica a followers automáticamente"
        status: Pending
        test_reference: "e2e/messaging/chatter.spec.ts:108"

    business_rules:
      - id: RN-001
        description: "Mensaje debe estar asociado a un modelo y registro (res_model, res_id)"
        implementation: "src/modules/messaging/dto/create-message.dto.ts:@IsNotEmpty()"
        test_reference: "src/modules/messaging/services/message.service.spec.ts:95"
      - id: RN-002
        description: "Tipos de mensaje: comment, notification, email, note"
        implementation: "database-design/schemas/system-schema-ddl.sql:messages.message_type"
        test_reference: "src/modules/messaging/services/message.service.spec.ts:122"
      - id: RN-003
        description: "Nuevo mensaje notifica a todos los followers del registro"
        implementation: "src/modules/messaging/services/message.service.ts:create()"
        test_reference: "src/modules/messaging/services/message.service.spec.ts:148"

    dependencies:
      rf_dependencies: []
      module_dependencies:
        - MGN-001
      external_dependencies:
        - name: "@nestjs/common"
          version: "^10.0.0"
        - name: "multer"
          version: "^1.4.5-lts.1"

  - rf_id: RF-MGN-014-002
    rf_title: "Notificaciones In-App y Email"
    rf_file: "requerimientos-funcionales/mgn-014/RF-MGN-014-002-notificaciones-in-app-y-email.md"
    priority: P0
    story_points: 8

    et_backend:
      file: "especificaciones-tecnicas/backend/mgn-014/ET-BACKEND-MGN-014-002-notificaciones-in-app-y-email.md"
      endpoints:
        - method: GET
          path: /api/v1/notifications
          description: "Listar notificaciones del usuario"
        - method: GET
          path: /api/v1/notifications/unread-count
          description: "Contar notificaciones no leídas"
        - method: POST
          path: /api/v1/notifications/:id/mark-read
          description: "Marcar como leída"
        - method: POST
          path: /api/v1/notifications/mark-all-read
          description: "Marcar todas como leídas"
        - method: DELETE
          path: /api/v1/notifications/:id
          description: "Eliminar notificación"
      services:
        - name: "NotificationService"
          file: "src/modules/messaging/services/notification.service.ts"
          methods:
            - findAll
            - getUnreadCount
            - markRead
            - markAllRead
            - remove
            - send
      controllers:
        - name: "NotificationController"
          file: "src/modules/messaging/controllers/notification.controller.ts"
      dtos:
        - name: "NotificationResponseDto"
          file: "src/modules/messaging/dto/notification-response.dto.ts"
        - name: "FilterNotificationDto"
          file: "src/modules/messaging/dto/filter-notification.dto.ts"

    et_frontend:
      file: "especificaciones-tecnicas/frontend/mgn-014/ET-FRONTEND-MGN-014-002-notificaciones-in-app-y-email.md"
      routes:
        - path: "/notifications"
          component: "NotificationsPage"
      components:
        - name: "NotificationBell"
          file: "src/widgets/notification-bell/ui/NotificationBell.tsx"
          type: widget
        - name: "NotificationList"
          file: "src/features/notification-list/ui/NotificationList.tsx"
          type: feature
        - name: "NotificationCard"
          file: "src/entities/notification/ui/NotificationCard.tsx"
          type: entity
        - name: "NotificationsPage"
          file: "src/pages/messaging/NotificationsPage.tsx"
          type: page
      api_client:
        - name: "notificationApi"
          file: "src/entities/notification/api/notification.api.ts"
          methods:
            - getAll
            - getUnreadCount
            - markRead
            - markAllRead
            - delete
      state_management:
        - name: "useNotificationStore"
          file: "src/entities/notification/model/notification.store.ts"
          type: zustand
        - name: "useNotifications"
          file: "src/entities/notification/api/notification.queries.ts"
          type: react-query

    database_tables:
      - schema: system
        table: notifications
        file: "database-design/schemas/system-schema-ddl.sql"
        operations:
          - SELECT
          - INSERT
          - UPDATE
          - DELETE
        indices:
          - idx_notifications_tenant_id
          - idx_notifications_user_id
          - idx_notifications_is_read
          - idx_notifications_created_at
        rls_policy: tenant_isolation_notifications

    tests:
      backend:
        unit_tests:
          - file: "src/modules/messaging/services/notification.service.spec.ts"
            test_cases:
              - "should send in-app notification"
              - "should send email notification"
              - "should mark notification as read"
              - "should get unread count"
              - "should delete old notifications"
        integration_tests:
          - file: "test/messaging/notification.controller.e2e-spec.ts"
            test_cases:
              - "GET /api/v1/notifications should return notifications"
              - "GET /api/v1/notifications/unread-count should return count"
              - "POST /api/v1/notifications/:id/mark-read should mark read"
              - "POST /api/v1/notifications/mark-all-read should mark all"
              - "DELETE /api/v1/notifications/:id should delete"
              - "should enforce tenant isolation"
              - "should require authentication"
      frontend:
        component_tests:
          - file: "src/widgets/notification-bell/ui/NotificationBell.test.tsx"
            test_cases:
              - "should show unread count badge"
              - "should open notification dropdown"
              - "should mark as read on click"
          - file: "src/features/notification-list/ui/NotificationList.test.tsx"
            test_cases:
              - "should render notifications"
              - "should mark all as read"
              - "should navigate to related record"
        e2e_tests:
          - file: "e2e/messaging/notifications.spec.ts"
            test_cases:
              - "should receive notification"
              - "should mark as read"
              - "should navigate to record"
              - "should receive email notification"

    acceptance_criteria:
      - id: AC-001
        description: "Usuario recibe notificaciones in-app en tiempo real"
        status: Pending
        test_reference: "test/messaging/notification.controller.e2e-spec.ts:42"
      - id: AC-002
        description: "Notificaciones se envían también por email según preferencias"
        status: Pending
        test_reference: "src/modules/messaging/services/notification.service.spec.ts:78"
      - id: AC-003
        description: "Badge muestra contador de notificaciones no leídas"
        status: Pending
        test_reference: "src/widgets/notification-bell/ui/NotificationBell.test.tsx:115"

    business_rules:
      - id: RN-001
        description: "Notificaciones se entregan por canal: in-app, email o ambos"
        implementation: "src/modules/messaging/services/notification.service.ts:send()"
        test_reference: "src/modules/messaging/services/notification.service.spec.ts:102"
      - id: RN-002
        description: "Usuario puede configurar preferencias de notificación"
        implementation: "database-design/schemas/auth-schema-ddl.sql:users.notification_preferences"
        test_reference: "src/modules/messaging/services/notification.service.spec.ts:128"
      - id: RN-003
        description: "Notificaciones in-app se eliminan automáticamente después de 30 días"
        implementation: "src/modules/messaging/services/notification.service.ts:cleanupOld()"
        test_reference: "src/modules/messaging/services/notification.service.spec.ts:155"

    dependencies:
      rf_dependencies: []
      module_dependencies:
        - MGN-001
      external_dependencies:
        - name: "nodemailer"
          version: "^6.9.0"
        - name: "socket.io"
          version: "^4.6.0"

  - rf_id: RF-MGN-014-003
    rf_title: "Tracking Automático de Cambios"
    rf_file: "requerimientos-funcionales/mgn-014/RF-MGN-014-003-tracking-automático-de-cambios.md"
    priority: P0
    story_points: 8

    et_backend:
      file: "especificaciones-tecnicas/backend/mgn-014/ET-BACKEND-MGN-014-003-tracking-automático-de-cambios.md"
      endpoints:
        - method: GET
          path: /api/v1/tracking/changes
          description: "Listar cambios de un registro"
        - method: GET
          path: /api/v1/tracking/changes/:id
          description: "Obtener cambio por ID"
        - method: GET
          path: /api/v1/tracking/audit-log
          description: "Audit log del sistema"
      services:
        - name: "TrackingService"
          file: "src/modules/messaging/services/tracking.service.ts"
          methods:
            - trackChange
            - findChanges
            - findOne
            - getAuditLog
            - generateMessage
      controllers:
        - name: "TrackingController"
          file: "src/modules/messaging/controllers/tracking.controller.ts"
      dtos:
        - name: "ChangeResponseDto"
          file: "src/modules/messaging/dto/change-response.dto.ts"
        - name: "FilterChangeDto"
          file: "src/modules/messaging/dto/filter-change.dto.ts"
        - name: "AuditLogDto"
          file: "src/modules/messaging/dto/audit-log.dto.ts"

    et_frontend:
      file: "especificaciones-tecnicas/frontend/mgn-014/ET-FRONTEND-MGN-014-003-tracking-automático-de-cambios.md"
      routes:
        - path: "/tracking/audit-log"
          component: "AuditLogPage"
        - path: "/*/:modelId/history"
          component: "RecordHistoryWidget"
      components:
        - name: "RecordHistoryWidget"
          file: "src/widgets/record-history-widget/ui/RecordHistoryWidget.tsx"
          type: widget
        - name: "ChangeTimeline"
          file: "src/features/change-timeline/ui/ChangeTimeline.tsx"
          type: feature
        - name: "ChangeCard"
          file: "src/entities/change/ui/ChangeCard.tsx"
          type: entity
        - name: "AuditLogPage"
          file: "src/pages/tracking/AuditLogPage.tsx"
          type: page
      api_client:
        - name: "trackingApi"
          file: "src/entities/tracking/api/tracking.api.ts"
          methods:
            - getChanges
            - getById
            - getAuditLog
      state_management:
        - name: "useTrackingStore"
          file: "src/entities/tracking/model/tracking.store.ts"
          type: zustand
        - name: "useTracking"
          file: "src/entities/tracking/api/tracking.queries.ts"
          type: react-query

    database_tables:
      - schema: system
        table: chatter_logs
        file: "database-design/schemas/system-schema-ddl.sql"
        operations:
          - SELECT
          - INSERT
        indices:
          - idx_chatter_logs_tenant_id
          - idx_chatter_logs_res_model_res_id
          - idx_chatter_logs_user_id
          - idx_chatter_logs_created_at
        rls_policy: tenant_isolation_chatter_logs

    tests:
      backend:
        unit_tests:
          - file: "src/modules/messaging/services/tracking.service.spec.ts"
            test_cases:
              - "should track field changes automatically"
              - "should generate readable change message"
              - "should store old and new values"
              - "should track create/update/delete operations"
              - "should filter sensitive fields"
        integration_tests:
          - file: "test/messaging/tracking.controller.e2e-spec.ts"
            test_cases:
              - "GET /api/v1/tracking/changes should return changes"
              - "GET /api/v1/tracking/changes/:id should return change"
              - "GET /api/v1/tracking/audit-log should return audit log"
              - "should track changes on entity updates"
              - "should enforce tenant isolation"
              - "should require authentication"
              - "should check permissions"
      frontend:
        component_tests:
          - file: "src/widgets/record-history-widget/ui/RecordHistoryWidget.test.tsx"
            test_cases:
              - "should render change history"
              - "should show field changes"
              - "should highlight modified fields"
          - file: "src/features/change-timeline/ui/ChangeTimeline.test.tsx"
            test_cases:
              - "should render timeline"
              - "should filter by date"
              - "should filter by user"
        e2e_tests:
          - file: "e2e/tracking/changes.spec.ts"
            test_cases:
              - "should track record updates"
              - "should view change history"
              - "should view audit log"
              - "should enforce permissions"

    acceptance_criteria:
      - id: AC-001
        description: "Sistema registra automáticamente cambios en registros"
        status: Pending
        test_reference: "test/messaging/tracking.controller.e2e-spec.ts:45"
      - id: AC-002
        description: "Historial muestra qué cambió, cuándo y quién lo cambió"
        status: Pending
        test_reference: "src/modules/messaging/services/tracking.service.spec.ts:82"
      - id: AC-003
        description: "Cambios se muestran en formato legible en chatter"
        status: Pending
        test_reference: "e2e/tracking/changes.spec.ts:118"

    business_rules:
      - id: RN-001
        description: "Solo campos tracked se registran en historial"
        implementation: "src/modules/messaging/services/tracking.service.ts:trackChange()"
        test_reference: "src/modules/messaging/services/tracking.service.spec.ts:105"
      - id: RN-002
        description: "Campos sensibles (passwords) no se registran"
        implementation: "src/modules/messaging/services/tracking.service.ts:filterSensitive()"
        test_reference: "src/modules/messaging/services/tracking.service.spec.ts:132"
      - id: RN-003
        description: "Cambios generan mensaje automático en chatter"
        implementation: "src/modules/messaging/services/tracking.service.ts:generateMessage()"
        test_reference: "src/modules/messaging/services/tracking.service.spec.ts:158"

    dependencies:
      rf_dependencies:
        - RF-MGN-014-001
      module_dependencies:
        - MGN-001
      external_dependencies:
        - name: "@nestjs/common"
          version: "^10.0.0"

  - rf_id: RF-MGN-014-004
    rf_title: "Actividades Programadas"
    rf_file: "requerimientos-funcionales/mgn-014/RF-MGN-014-004-actividades-programadas.md"
    priority: P1
    story_points: 8

    et_backend:
      file: "especificaciones-tecnicas/backend/mgn-014/ET-BACKEND-MGN-014-004-actividades-programadas.md"
      endpoints:
        - method: POST
          path: /api/v1/activities
          description: "Crear actividad"
        - method: GET
          path: /api/v1/activities
          description: "Listar actividades"
        - method: GET
          path: /api/v1/activities/:id
          description: "Obtener actividad por ID"
        - method: PUT
          path: /api/v1/activities/:id
          description: "Actualizar actividad"
        - method: POST
          path: /api/v1/activities/:id/complete
          description: "Marcar como completada"
      services:
        - name: "ActivityService"
          file: "src/modules/messaging/services/activity.service.ts"
          methods:
            - create
            - findAll
            - findOne
            - update
            - complete
            - validateBusinessRules
      controllers:
        - name: "ActivityController"
          file: "src/modules/messaging/controllers/activity.controller.ts"
      dtos:
        - name: "CreateActivityDto"
          file: "src/modules/messaging/dto/create-activity.dto.ts"
        - name: "UpdateActivityDto"
          file: "src/modules/messaging/dto/update-activity.dto.ts"
        - name: "ActivityResponseDto"
          file: "src/modules/messaging/dto/activity-response.dto.ts"
        - name: "FilterActivityDto"
          file: "src/modules/messaging/dto/filter-activity.dto.ts"

    et_frontend:
      file: "especificaciones-tecnicas/frontend/mgn-014/ET-FRONTEND-MGN-014-004-actividades-programadas.md"
      routes:
        - path: "/activities"
          component: "ActivitiesPage"
        - path: "/activities/:id"
          component: "ViewActivityPage"
      components:
        - name: "ActivitiesCalendar"
          file: "src/widgets/activities-calendar/ui/ActivitiesCalendar.tsx"
          type: widget
        - name: "CreateActivityForm"
          file: "src/features/create-activity/ui/CreateActivityForm.tsx"
          type: feature
        - name: "ActivityCard"
          file: "src/entities/activity/ui/ActivityCard.tsx"
          type: entity
        - name: "ActivitiesPage"
          file: "src/pages/activities/ActivitiesPage.tsx"
          type: page
      api_client:
        - name: "activityApi"
          file: "src/entities/activity/api/activity.api.ts"
          methods:
            - getAll
            - getById
            - create
            - update
            - complete
      state_management:
        - name: "useActivityStore"
          file: "src/entities/activity/model/activity.store.ts"
          type: zustand
        - name: "useActivities"
          file: "src/entities/activity/api/activity.queries.ts"
          type: react-query

    database_tables:
      - schema: system
        table: activities
        file: "database-design/schemas/system-schema-ddl.sql"
        operations:
          - SELECT
          - INSERT
          - UPDATE
          - DELETE (soft)
        indices:
          - idx_activities_tenant_id
          - idx_activities_res_model_res_id
          - idx_activities_assigned_to
          - idx_activities_due_date
        rls_policy: tenant_isolation_activities

    tests:
      backend:
        unit_tests:
          - file: "src/modules/messaging/services/activity.service.spec.ts"
            test_cases:
              - "should create activity linked to record"
              - "should send notification on activity assignment"
              - "should complete activity successfully"
              - "should send reminder before due date"
              - "should find overdue activities"
        integration_tests:
          - file: "test/messaging/activity.controller.e2e-spec.ts"
            test_cases:
              - "POST /api/v1/activities should create activity"
              - "GET /api/v1/activities should return activities"
              - "GET /api/v1/activities/:id should return activity"
              - "PUT /api/v1/activities/:id should update activity"
              - "POST /api/v1/activities/:id/complete should complete"
              - "should enforce tenant isolation"
              - "should require authentication"
              - "should check permissions"
      frontend:
        component_tests:
          - file: "src/widgets/activities-calendar/ui/ActivitiesCalendar.test.tsx"
            test_cases:
              - "should render calendar view"
              - "should show activities by date"
              - "should create activity from calendar"
          - file: "src/features/create-activity/ui/CreateActivityForm.test.tsx"
            test_cases:
              - "should validate required fields"
              - "should submit valid form"
              - "should show error messages"
        e2e_tests:
          - file: "e2e/activities/activities.spec.ts"
            test_cases:
              - "should create activity successfully"
              - "should complete activity"
              - "should receive reminders"
              - "should enforce permissions"

    acceptance_criteria:
      - id: AC-001
        description: "Usuario puede crear actividades vinculadas a cualquier registro"
        status: Pending
        test_reference: "test/messaging/activity.controller.e2e-spec.ts:48"
      - id: AC-002
        description: "Sistema envía recordatorios antes de fecha límite"
        status: Pending
        test_reference: "src/modules/messaging/services/activity.service.spec.ts:85"
      - id: AC-003
        description: "Actividades se muestran en calendario y lista"
        status: Pending
        test_reference: "src/widgets/activities-calendar/ui/ActivitiesCalendar.test.tsx:122"

    business_rules:
      - id: RN-001
        description: "Actividad debe estar vinculada a un registro (res_model, res_id)"
        implementation: "src/modules/messaging/dto/create-activity.dto.ts:@IsNotEmpty()"
        test_reference: "src/modules/messaging/services/activity.service.spec.ts:108"
      - id: RN-002
        description: "Recordatorio se envía 1 día antes de due_date"
        implementation: "src/modules/messaging/services/activity.service.ts:sendReminders()"
        test_reference: "src/modules/messaging/services/activity.service.spec.ts:135"
      - id: RN-003
        description: "Actividad completada genera mensaje en chatter"
        implementation: "src/modules/messaging/services/activity.service.ts:complete()"
        test_reference: "src/modules/messaging/services/activity.service.spec.ts:162"

    dependencies:
      rf_dependencies:
        - RF-MGN-014-001
        - RF-MGN-014-002
      module_dependencies:
        - MGN-001
      external_dependencies:
        - name: "@nestjs/schedule"
          version: "^4.0.0"

  - rf_id: RF-MGN-014-005
    rf_title: "Followers (Seguidores)"
    rf_file: "requerimientos-funcionales/mgn-014/RF-MGN-014-005-followers-seguidores.md"
    priority: P1
    story_points: 5

    et_backend:
      file: "especificaciones-tecnicas/backend/mgn-014/ET-BACKEND-MGN-014-005-followers-seguidores.md"
      endpoints:
        - method: POST
          path: /api/v1/followers
          description: "Agregar follower a registro"
        - method: GET
          path: /api/v1/followers
          description: "Listar followers de un registro"
        - method: DELETE
          path: /api/v1/followers/:id
          description: "Eliminar follower"
        - method: POST
          path: /api/v1/followers/subscribe
          description: "Seguir registro"
        - method: POST
          path: /api/v1/followers/unsubscribe
          description: "Dejar de seguir registro"
      services:
        - name: "FollowerService"
          file: "src/modules/messaging/services/follower.service.ts"
          methods:
            - addFollower
            - findFollowers
            - removeFollower
            - subscribe
            - unsubscribe
            - notifyFollowers
      controllers:
        - name: "FollowerController"
          file: "src/modules/messaging/controllers/follower.controller.ts"
      dtos:
        - name: "AddFollowerDto"
          file: "src/modules/messaging/dto/add-follower.dto.ts"
        - name: "FollowerResponseDto"
          file: "src/modules/messaging/dto/follower-response.dto.ts"
        - name: "SubscribeDto"
          file: "src/modules/messaging/dto/subscribe.dto.ts"

    et_frontend:
      file: "especificaciones-tecnicas/frontend/mgn-014/ET-FRONTEND-MGN-014-005-followers-seguidores.md"
      routes:
        - path: "/*/:modelId/followers"
          component: "FollowersWidget"
      components:
        - name: "FollowersWidget"
          file: "src/widgets/followers-widget/ui/FollowersWidget.tsx"
          type: widget
        - name: "AddFollowerButton"
          file: "src/features/add-follower/ui/AddFollowerButton.tsx"
          type: feature
        - name: "FollowerCard"
          file: "src/entities/follower/ui/FollowerCard.tsx"
          type: entity
        - name: "FollowersPage"
          file: "src/pages/followers/FollowersPage.tsx"
          type: page
      api_client:
        - name: "followerApi"
          file: "src/entities/follower/api/follower.api.ts"
          methods:
            - getFollowers
            - addFollower
            - removeFollower
            - subscribe
            - unsubscribe
      state_management:
        - name: "useFollowerStore"
          file: "src/entities/follower/model/follower.store.ts"
          type: zustand
        - name: "useFollowers"
          file: "src/entities/follower/api/follower.queries.ts"
          type: react-query

    database_tables:
      - schema: system
        table: message_followers
        file: "database-design/schemas/system-schema-ddl.sql"
        operations:
          - SELECT
          - INSERT
          - DELETE
        indices:
          - idx_message_followers_tenant_id
          - idx_message_followers_res_model_res_id
          - idx_message_followers_user_id
        rls_policy: tenant_isolation_message_followers

    tests:
      backend:
        unit_tests:
          - file: "src/modules/messaging/services/follower.service.spec.ts"
            test_cases:
              - "should add follower to record"
              - "should remove follower from record"
              - "should notify followers on updates"
              - "should prevent duplicate followers"
              - "should list followers of record"
        integration_tests:
          - file: "test/messaging/follower.controller.e2e-spec.ts"
            test_cases:
              - "POST /api/v1/followers should add follower"
              - "GET /api/v1/followers should return followers"
              - "DELETE /api/v1/followers/:id should remove follower"
              - "POST /api/v1/followers/subscribe should subscribe"
              - "POST /api/v1/followers/unsubscribe should unsubscribe"
              - "should enforce tenant isolation"
              - "should require authentication"
      frontend:
        component_tests:
          - file: "src/widgets/followers-widget/ui/FollowersWidget.test.tsx"
            test_cases:
              - "should render followers list"
              - "should add follower"
              - "should remove follower"
          - file: "src/features/add-follower/ui/AddFollowerButton.test.tsx"
            test_cases:
              - "should show user selector"
              - "should add selected users"
              - "should subscribe self"
        e2e_tests:
          - file: "e2e/followers/followers.spec.ts"
            test_cases:
              - "should add follower successfully"
              - "should receive notifications as follower"
              - "should unsubscribe from record"
              - "should enforce permissions"

    acceptance_criteria:
      - id: AC-001
        description: "Usuario puede seguir cualquier registro para recibir actualizaciones"
        status: Pending
        test_reference: "test/messaging/follower.controller.e2e-spec.ts:52"
      - id: AC-002
        description: "Followers reciben notificaciones de mensajes y cambios"
        status: Pending
        test_reference: "src/modules/messaging/services/follower.service.spec.ts:88"
      - id: AC-003
        description: "Usuario puede dejar de seguir registro en cualquier momento"
        status: Pending
        test_reference: "e2e/followers/followers.spec.ts:125"

    business_rules:
      - id: RN-001
        description: "Usuario no puede ser follower duplicado del mismo registro"
        implementation: "database-design/schemas/system-schema-ddl.sql:CONSTRAINT uq_followers"
        test_reference: "src/modules/messaging/services/follower.service.spec.ts:112"
      - id: RN-002
        description: "Creador de registro se convierte en follower automáticamente"
        implementation: "src/modules/messaging/services/follower.service.ts:addCreatorAsFollower()"
        test_reference: "src/modules/messaging/services/follower.service.spec.ts:138"
      - id: RN-003
        description: "Followers reciben notificación en cada mensaje nuevo"
        implementation: "src/modules/messaging/services/message.service.ts:notifyFollowers()"
        test_reference: "src/modules/messaging/services/follower.service.spec.ts:165"

    dependencies:
      rf_dependencies:
        - RF-MGN-014-001
        - RF-MGN-014-002
      module_dependencies:
        - MGN-001
      external_dependencies:
        - name: "@nestjs/common"
          version: "^10.0.0"

  - rf_id: RF-MGN-014-006
    rf_title: "Templates de Email"
    rf_file: "requerimientos-funcionales/mgn-014/RF-MGN-014-006-templates-de-email.md"
    priority: P1
    story_points: 5

    et_backend:
      file: "especificaciones-tecnicas/backend/mgn-014/ET-BACKEND-MGN-014-006-templates-de-email.md"
      endpoints:
        - method: POST
          path: /api/v1/email-templates
          description: "Crear template"
        - method: GET
          path: /api/v1/email-templates
          description: "Listar templates"
        - method: GET
          path: /api/v1/email-templates/:id
          description: "Obtener template por ID"
        - method: PUT
          path: /api/v1/email-templates/:id
          description: "Actualizar template"
        - method: POST
          path: /api/v1/email-templates/:id/preview
          description: "Vista previa del template"
      services:
        - name: "EmailTemplateService"
          file: "src/modules/messaging/services/email-template.service.ts"
          methods:
            - create
            - findAll
            - findOne
            - update
            - preview
            - render
      controllers:
        - name: "EmailTemplateController"
          file: "src/modules/messaging/controllers/email-template.controller.ts"
      dtos:
        - name: "CreateEmailTemplateDto"
          file: "src/modules/messaging/dto/create-email-template.dto.ts"
        - name: "UpdateEmailTemplateDto"
          file: "src/modules/messaging/dto/update-email-template.dto.ts"
        - name: "EmailTemplateResponseDto"
          file: "src/modules/messaging/dto/email-template-response.dto.ts"
        - name: "PreviewTemplateDto"
          file: "src/modules/messaging/dto/preview-template.dto.ts"

    et_frontend:
      file: "especificaciones-tecnicas/frontend/mgn-014/ET-FRONTEND-MGN-014-006-templates-de-email.md"
      routes:
        - path: "/email-templates"
          component: "EmailTemplatesPage"
        - path: "/email-templates/create"
          component: "CreateEmailTemplatePage"
        - path: "/email-templates/:id/edit"
          component: "EditEmailTemplatePage"
      components:
        - name: "EmailTemplatesTable"
          file: "src/widgets/email-templates-table/ui/EmailTemplatesTable.tsx"
          type: widget
        - name: "TemplateEditor"
          file: "src/features/template-editor/ui/TemplateEditor.tsx"
          type: feature
        - name: "EmailTemplateCard"
          file: "src/entities/email-template/ui/EmailTemplateCard.tsx"
          type: entity
        - name: "EmailTemplatesPage"
          file: "src/pages/messaging/EmailTemplatesPage.tsx"
          type: page
      api_client:
        - name: "emailTemplateApi"
          file: "src/entities/email-template/api/email-template.api.ts"
          methods:
            - getAll
            - getById
            - create
            - update
            - preview
      state_management:
        - name: "useEmailTemplateStore"
          file: "src/entities/email-template/model/email-template.store.ts"
          type: zustand
        - name: "useEmailTemplates"
          file: "src/entities/email-template/api/email-template.queries.ts"
          type: react-query

    database_tables:
      - schema: system
        table: email_templates
        file: "database-design/schemas/system-schema-ddl.sql"
        operations:
          - SELECT
          - INSERT
          - UPDATE
          - DELETE (soft)
        indices:
          - idx_email_templates_tenant_id
          - idx_email_templates_model
        rls_policy: tenant_isolation_email_templates

    tests:
      backend:
        unit_tests:
          - file: "src/modules/messaging/services/email-template.service.spec.ts"
            test_cases:
              - "should create email template with variables"
              - "should render template with data"
              - "should validate template syntax"
              - "should preview template"
              - "should support HTML and plain text"
        integration_tests:
          - file: "test/messaging/email-template.controller.e2e-spec.ts"
            test_cases:
              - "POST /api/v1/email-templates should create template"
              - "GET /api/v1/email-templates should return templates"
              - "GET /api/v1/email-templates/:id should return template"
              - "PUT /api/v1/email-templates/:id should update template"
              - "POST /api/v1/email-templates/:id/preview should preview"
              - "should enforce tenant isolation"
              - "should require authentication"
              - "should check permissions"
      frontend:
        component_tests:
          - file: "src/widgets/email-templates-table/ui/EmailTemplatesTable.test.tsx"
            test_cases:
              - "should render templates"
              - "should filter by model"
              - "should preview template"
          - file: "src/features/template-editor/ui/TemplateEditor.test.tsx"
            test_cases:
              - "should edit template HTML"
              - "should insert variables"
              - "should preview rendering"
        e2e_tests:
          - file: "e2e/messaging/email-templates.spec.ts"
            test_cases:
              - "should create template successfully"
              - "should edit template successfully"
              - "should preview with sample data"
              - "should enforce permissions"

    acceptance_criteria:
      - id: AC-001
        description: "Usuario puede crear templates con variables dinámicas"
        status: Pending
        test_reference: "test/messaging/email-template.controller.e2e-spec.ts:55"
      - id: AC-002
        description: "Templates soportan sintaxis Handlebars para variables"
        status: Pending
        test_reference: "src/modules/messaging/services/email-template.service.spec.ts:92"
      - id: AC-003
        description: "Sistema valida sintaxis al guardar template"
        status: Pending
        test_reference: "e2e/messaging/email-templates.spec.ts:128"

    business_rules:
      - id: RN-001
        description: "Template debe estar asociado a un modelo (ej: invoice, quotation)"
        implementation: "src/modules/messaging/dto/create-email-template.dto.ts:@IsNotEmpty()"
        test_reference: "src/modules/messaging/services/email-template.service.spec.ts:115"
      - id: RN-002
        description: "Variables se reemplazan usando sintaxis {{ variable }}"
        implementation: "src/modules/messaging/services/email-template.service.ts:render()"
        test_reference: "src/modules/messaging/services/email-template.service.spec.ts:142"
      - id: RN-003
        description: "Template invalido no puede guardarse (validación sintaxis)"
        implementation: "src/modules/messaging/services/email-template.service.ts:validateSyntax()"
        test_reference: "src/modules/messaging/services/email-template.service.spec.ts:168"

    dependencies:
      rf_dependencies: []
      module_dependencies:
        - MGN-001
      external_dependencies:
        - name: "handlebars"
          version: "^4.7.0"
        - 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: 30
  total_components: 24
  total_tables: 6
  total_test_cases: 120
  estimated_duration_sprints: 3
