1093 lines
41 KiB
YAML
1093 lines
41 KiB
YAML
# 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
|