# 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