# TRACEABILITY-MGN-009.yaml # Matriz de Trazabilidad - MGN-009: CRM Básico # Fecha: 2025-11-24 # Versión: 1.0 module: id: MGN-009 name: "CRM Básico" description: "Sistema de gestión de relaciones con clientes: leads, oportunidades, pipeline, actividades y conversión" priority: P1 story_points: 31 status: Diseñado metadata: total_rf: 5 total_et_backend: 5 total_et_frontend: 5 total_tables: 5 total_tests: 100 coverage: 100% requirements: - rf_id: RF-MGN-009-001 rf_title: "Gestión de Leads y Oportunidades" rf_file: "requerimientos-funcionales/mgn-009/RF-MGN-009-001-gestión-de-leads-y-oportunidades.md" priority: P1 story_points: 8 et_backend: file: "especificaciones-tecnicas/backend/mgn-009/ET-BACKEND-MGN-009-001-gestión-de-leads-y-oportunidades.md" endpoints: - method: POST path: /api/v1/crm/leads description: "Crear nuevo lead" - method: GET path: /api/v1/crm/leads description: "Listar todos los leads" - method: GET path: /api/v1/crm/leads/:id description: "Obtener lead por ID" - method: PUT path: /api/v1/crm/leads/:id description: "Actualizar lead" - method: DELETE path: /api/v1/crm/leads/:id description: "Eliminar lead (soft delete)" services: - name: "LeadService" file: "src/modules/crm/services/lead.service.ts" methods: - create - findAll - findOne - update - remove - validateBusinessRules controllers: - name: "LeadController" file: "src/modules/crm/controllers/lead.controller.ts" dtos: - name: "CreateLeadDto" file: "src/modules/crm/dto/create-lead.dto.ts" - name: "UpdateLeadDto" file: "src/modules/crm/dto/update-lead.dto.ts" - name: "LeadResponseDto" file: "src/modules/crm/dto/lead-response.dto.ts" - name: "FilterLeadDto" file: "src/modules/crm/dto/filter-lead.dto.ts" et_frontend: file: "especificaciones-tecnicas/frontend/mgn-009/ET-FRONTEND-MGN-009-001-gestión-de-leads-y-oportunidades.md" routes: - path: "/crm/leads" component: "LeadsPage" - path: "/crm/leads/create" component: "CreateLeadPage" - path: "/crm/leads/:id/edit" component: "EditLeadPage" - path: "/crm/leads/:id" component: "ViewLeadPage" components: - name: "LeadsTable" file: "src/widgets/leads-table/ui/LeadsTable.tsx" type: widget - name: "CreateLeadForm" file: "src/features/create-lead/ui/CreateLeadForm.tsx" type: feature - name: "LeadCard" file: "src/entities/lead/ui/LeadCard.tsx" type: entity - name: "LeadPage" file: "src/pages/crm/LeadPage.tsx" type: page api_client: - name: "leadApi" file: "src/entities/lead/api/lead.api.ts" methods: - getAll - getById - create - update - delete state_management: - name: "useLeadStore" file: "src/entities/lead/model/lead.store.ts" type: zustand - name: "useLeads" file: "src/entities/lead/api/lead.queries.ts" type: react-query database_tables: - schema: sales table: leads file: "database-design/schemas/sales-schema-ddl.sql" operations: - SELECT - INSERT - UPDATE - DELETE (soft) indices: - idx_leads_tenant_id - idx_leads_stage_id - idx_leads_user_id - idx_leads_partner_id rls_policy: tenant_isolation_leads tests: backend: unit_tests: - file: "src/modules/crm/services/lead.service.spec.ts" test_cases: - "should create lead with valid data" - "should assign lead to user automatically" - "should validate email format" - "should find all leads for tenant" - "should update lead successfully" integration_tests: - file: "test/crm/lead.controller.e2e-spec.ts" test_cases: - "POST /api/v1/crm/leads should create lead" - "GET /api/v1/crm/leads should return all leads" - "GET /api/v1/crm/leads/:id should return lead" - "PUT /api/v1/crm/leads/:id should update lead" - "DELETE /api/v1/crm/leads/:id should soft delete lead" - "should enforce tenant isolation" - "should require authentication" - "should check permissions" frontend: component_tests: - file: "src/widgets/leads-table/ui/LeadsTable.test.tsx" test_cases: - "should render table with leads" - "should handle pagination" - "should filter by stage" - file: "src/features/create-lead/ui/CreateLeadForm.test.tsx" test_cases: - "should validate required fields" - "should submit valid form" - "should show error messages" e2e_tests: - file: "e2e/crm/leads.spec.ts" test_cases: - "should create lead successfully" - "should edit lead successfully" - "should delete lead with confirmation" - "should enforce permissions" acceptance_criteria: - id: AC-001 description: "Usuario puede crear leads con información de contacto" status: Pending test_reference: "test/crm/lead.controller.e2e-spec.ts:35" - id: AC-002 description: "Leads se asignan automáticamente a usuarios según reglas" status: Pending test_reference: "src/modules/crm/services/lead.service.spec.ts:72" - id: AC-003 description: "Sistema detecta y previene leads duplicados por email" status: Pending test_reference: "test/crm/lead.controller.e2e-spec.ts:108" business_rules: - id: RN-001 description: "Email debe ser único por tenant (detectar duplicados)" implementation: "src/modules/crm/services/lead.service.ts:validateEmail()" test_reference: "src/modules/crm/services/lead.service.spec.ts:95" - id: RN-002 description: "Lead debe tener al menos nombre y email o teléfono" implementation: "src/modules/crm/dto/create-lead.dto.ts:@IsNotEmpty()" test_reference: "src/modules/crm/services/lead.service.spec.ts:122" - id: RN-003 description: "Lead sin asignar debe tener usuario por defecto (round-robin)" implementation: "src/modules/crm/services/lead.service.ts:assignUser()" test_reference: "src/modules/crm/services/lead.service.spec.ts:148" dependencies: rf_dependencies: - RF-MGN-003-001 module_dependencies: - MGN-001 - MGN-003 external_dependencies: - name: "@nestjs/common" version: "^10.0.0" - name: "ant-design" version: "^5.0.0" - rf_id: RF-MGN-009-002 rf_title: "Pipeline de Ventas (Kanban)" rf_file: "requerimientos-funcionales/mgn-009/RF-MGN-009-002-pipeline-de-ventas-kanban.md" priority: P1 story_points: 8 et_backend: file: "especificaciones-tecnicas/backend/mgn-009/ET-BACKEND-MGN-009-002-pipeline-de-ventas-kanban.md" endpoints: - method: GET path: /api/v1/crm/pipeline description: "Obtener pipeline con stages y oportunidades" - method: POST path: /api/v1/crm/stages description: "Crear nuevo stage" - method: PUT path: /api/v1/crm/stages/:id description: "Actualizar stage" - method: POST path: /api/v1/crm/opportunities/:id/move description: "Mover oportunidad a otro stage" - method: GET path: /api/v1/crm/opportunities description: "Listar oportunidades" services: - name: "PipelineService" file: "src/modules/crm/services/pipeline.service.ts" methods: - getPipeline - createStage - updateStage - moveOpportunity - calculateMetrics controllers: - name: "PipelineController" file: "src/modules/crm/controllers/pipeline.controller.ts" dtos: - name: "CreateStageDto" file: "src/modules/crm/dto/create-stage.dto.ts" - name: "MoveOpportunityDto" file: "src/modules/crm/dto/move-opportunity.dto.ts" - name: "PipelineResponseDto" file: "src/modules/crm/dto/pipeline-response.dto.ts" et_frontend: file: "especificaciones-tecnicas/frontend/mgn-009/ET-FRONTEND-MGN-009-002-pipeline-de-ventas-kanban.md" routes: - path: "/crm/pipeline" component: "PipelinePage" - path: "/crm/opportunities/:id" component: "OpportunityPage" components: - name: "PipelineKanban" file: "src/widgets/pipeline-kanban/ui/PipelineKanban.tsx" type: widget - name: "OpportunityCard" file: "src/entities/opportunity/ui/OpportunityCard.tsx" type: entity - name: "StageColumn" file: "src/features/pipeline-stage/ui/StageColumn.tsx" type: feature - name: "PipelinePage" file: "src/pages/crm/PipelinePage.tsx" type: page api_client: - name: "pipelineApi" file: "src/entities/pipeline/api/pipeline.api.ts" methods: - getPipeline - createStage - updateStage - moveOpportunity state_management: - name: "usePipelineStore" file: "src/entities/pipeline/model/pipeline.store.ts" type: zustand - name: "usePipeline" file: "src/entities/pipeline/api/pipeline.queries.ts" type: react-query database_tables: - schema: sales table: pipeline_stages file: "database-design/schemas/sales-schema-ddl.sql" operations: - SELECT - INSERT - UPDATE - DELETE (soft) indices: - idx_pipeline_stages_tenant_id - idx_pipeline_stages_sequence rls_policy: tenant_isolation_pipeline_stages - schema: sales table: opportunities file: "database-design/schemas/sales-schema-ddl.sql" operations: - SELECT - INSERT - UPDATE indices: - idx_opportunities_tenant_id - idx_opportunities_stage_id - idx_opportunities_user_id rls_policy: tenant_isolation_opportunities tests: backend: unit_tests: - file: "src/modules/crm/services/pipeline.service.spec.ts" test_cases: - "should get pipeline with stages and opportunities" - "should create new stage with sequence" - "should move opportunity between stages" - "should calculate pipeline metrics" - "should update stage order" integration_tests: - file: "test/crm/pipeline.controller.e2e-spec.ts" test_cases: - "GET /api/v1/crm/pipeline should return pipeline" - "POST /api/v1/crm/stages should create stage" - "PUT /api/v1/crm/stages/:id should update stage" - "POST /api/v1/crm/opportunities/:id/move should move opportunity" - "should enforce tenant isolation" - "should require authentication" - "should check permissions" frontend: component_tests: - file: "src/widgets/pipeline-kanban/ui/PipelineKanban.test.tsx" test_cases: - "should render kanban board with stages" - "should drag and drop opportunities" - "should show stage metrics" - file: "src/features/pipeline-stage/ui/StageColumn.test.tsx" test_cases: - "should render stage column" - "should handle drop event" - "should show opportunities count" e2e_tests: - file: "e2e/crm/pipeline.spec.ts" test_cases: - "should display pipeline kanban" - "should drag opportunity to new stage" - "should create new stage" - "should enforce permissions" acceptance_criteria: - id: AC-001 description: "Usuario puede visualizar pipeline en formato Kanban" status: Pending test_reference: "test/crm/pipeline.controller.e2e-spec.ts:42" - id: AC-002 description: "Oportunidades se mueven entre stages con drag & drop" status: Pending test_reference: "src/widgets/pipeline-kanban/ui/PipelineKanban.test.tsx:78" - id: AC-003 description: "Sistema calcula métricas por stage (cantidad, valor total)" status: Pending test_reference: "src/modules/crm/services/pipeline.service.spec.ts:115" business_rules: - id: RN-001 description: "Stages deben tener secuencia única y ordenada" implementation: "src/modules/crm/services/pipeline.service.ts:validateSequence()" test_reference: "src/modules/crm/services/pipeline.service.spec.ts:138" - id: RN-002 description: "Al mover oportunidad, se registra actividad automáticamente" implementation: "src/modules/crm/services/pipeline.service.ts:moveOpportunity()" test_reference: "src/modules/crm/services/pipeline.service.spec.ts:165" - id: RN-003 description: "Pipeline calcula tasa de conversión entre stages" implementation: "src/modules/crm/services/pipeline.service.ts:calculateMetrics()" test_reference: "src/modules/crm/services/pipeline.service.spec.ts:192" dependencies: rf_dependencies: - RF-MGN-009-001 module_dependencies: - MGN-001 external_dependencies: - name: "@dnd-kit/core" version: "^6.0.0" - name: "@dnd-kit/sortable" version: "^7.0.0" - rf_id: RF-MGN-009-003 rf_title: "Actividades y Seguimiento" rf_file: "requerimientos-funcionales/mgn-009/RF-MGN-009-003-actividades-y-seguimiento.md" priority: P1 story_points: 5 et_backend: file: "especificaciones-tecnicas/backend/mgn-009/ET-BACKEND-MGN-009-003-actividades-y-seguimiento.md" endpoints: - method: POST path: /api/v1/crm/activities description: "Crear nueva actividad" - method: GET path: /api/v1/crm/activities description: "Listar todas las actividades" - method: GET path: /api/v1/crm/activities/:id description: "Obtener actividad por ID" - method: PUT path: /api/v1/crm/activities/:id description: "Actualizar actividad" - method: POST path: /api/v1/crm/activities/:id/complete description: "Marcar actividad como completada" services: - name: "ActivityService" file: "src/modules/crm/services/activity.service.ts" methods: - create - findAll - findOne - update - complete - validateBusinessRules controllers: - name: "ActivityController" file: "src/modules/crm/controllers/activity.controller.ts" dtos: - name: "CreateActivityDto" file: "src/modules/crm/dto/create-activity.dto.ts" - name: "UpdateActivityDto" file: "src/modules/crm/dto/update-activity.dto.ts" - name: "ActivityResponseDto" file: "src/modules/crm/dto/activity-response.dto.ts" - name: "FilterActivityDto" file: "src/modules/crm/dto/filter-activity.dto.ts" et_frontend: file: "especificaciones-tecnicas/frontend/mgn-009/ET-FRONTEND-MGN-009-003-actividades-y-seguimiento.md" routes: - path: "/crm/activities" component: "ActivitiesPage" - path: "/crm/activities/create" component: "CreateActivityPage" - path: "/crm/activities/:id" component: "ViewActivityPage" components: - name: "ActivitiesTable" file: "src/widgets/activities-table/ui/ActivitiesTable.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: "ActivityPage" file: "src/pages/crm/ActivityPage.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: sales table: activities file: "database-design/schemas/sales-schema-ddl.sql" operations: - SELECT - INSERT - UPDATE - DELETE (soft) indices: - idx_activities_tenant_id - idx_activities_lead_id - idx_activities_opportunity_id - idx_activities_user_id - idx_activities_due_date rls_policy: tenant_isolation_activities tests: backend: unit_tests: - file: "src/modules/crm/services/activity.service.spec.ts" test_cases: - "should create activity with valid data" - "should send notification on activity assignment" - "should complete activity successfully" - "should find overdue activities" - "should update activity successfully" integration_tests: - file: "test/crm/activity.controller.e2e-spec.ts" test_cases: - "POST /api/v1/crm/activities should create activity" - "GET /api/v1/crm/activities should return all activities" - "GET /api/v1/crm/activities/:id should return activity" - "PUT /api/v1/crm/activities/:id should update activity" - "POST /api/v1/crm/activities/:id/complete should mark complete" - "should enforce tenant isolation" - "should require authentication" - "should check permissions" frontend: component_tests: - file: "src/widgets/activities-table/ui/ActivitiesTable.test.tsx" test_cases: - "should render table with activities" - "should filter by status" - "should show overdue activities" - 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/crm/activities.spec.ts" test_cases: - "should create activity successfully" - "should complete activity successfully" - "should show overdue notifications" - "should enforce permissions" acceptance_criteria: - id: AC-001 description: "Usuario puede crear actividades para leads y oportunidades" status: Pending test_reference: "test/crm/activity.controller.e2e-spec.ts:38" - id: AC-002 description: "Sistema envía notificaciones de actividades pendientes" status: Pending test_reference: "src/modules/crm/services/activity.service.spec.ts:75" - id: AC-003 description: "Actividades vencidas se resaltan en dashboard" status: Pending test_reference: "e2e/crm/activities.spec.ts:112" business_rules: - id: RN-001 description: "Actividad debe estar asociada a lead u oportunidad" implementation: "src/modules/crm/services/activity.service.ts:validateRelation()" test_reference: "src/modules/crm/services/activity.service.spec.ts:98" - id: RN-002 description: "Actividad vencida genera notificación automática" implementation: "src/modules/crm/services/activity.service.ts:checkOverdue()" test_reference: "src/modules/crm/services/activity.service.spec.ts:125" - id: RN-003 description: "Al completar actividad, se puede sugerir siguiente acción" implementation: "src/modules/crm/services/activity.service.ts:complete()" test_reference: "src/modules/crm/services/activity.service.spec.ts:152" dependencies: rf_dependencies: - RF-MGN-009-001 - RF-MGN-014-002 module_dependencies: - MGN-001 - MGN-014 external_dependencies: - name: "@nestjs/schedule" version: "^4.0.0" - rf_id: RF-MGN-009-004 rf_title: "Lead Scoring y Calificación" rf_file: "requerimientos-funcionales/mgn-009/RF-MGN-009-004-lead-scoring-y-calificación.md" priority: P2 story_points: 5 et_backend: file: "especificaciones-tecnicas/backend/mgn-009/ET-BACKEND-MGN-009-004-lead-scoring-y-calificación.md" endpoints: - method: POST path: /api/v1/crm/scoring-rules description: "Crear regla de scoring" - method: GET path: /api/v1/crm/scoring-rules description: "Listar reglas de scoring" - method: PUT path: /api/v1/crm/scoring-rules/:id description: "Actualizar regla" - method: POST path: /api/v1/crm/leads/:id/calculate-score description: "Calcular score de lead" - method: POST path: /api/v1/crm/leads/:id/qualify description: "Calificar lead" services: - name: "LeadScoringService" file: "src/modules/crm/services/lead-scoring.service.ts" methods: - createRule - updateRule - calculateScore - qualifyLead - autoAssignByScore controllers: - name: "LeadScoringController" file: "src/modules/crm/controllers/lead-scoring.controller.ts" dtos: - name: "CreateScoringRuleDto" file: "src/modules/crm/dto/create-scoring-rule.dto.ts" - name: "QualifyLeadDto" file: "src/modules/crm/dto/qualify-lead.dto.ts" - name: "LeadScoreResponseDto" file: "src/modules/crm/dto/lead-score-response.dto.ts" et_frontend: file: "especificaciones-tecnicas/frontend/mgn-009/ET-FRONTEND-MGN-009-004-lead-scoring-y-calificación.md" routes: - path: "/crm/scoring-rules" component: "ScoringRulesPage" - path: "/crm/leads/:id/scoring" component: "LeadScoringPage" components: - name: "ScoringRulesTable" file: "src/widgets/scoring-rules-table/ui/ScoringRulesTable.tsx" type: widget - name: "LeadScoreCard" file: "src/features/lead-scoring/ui/LeadScoreCard.tsx" type: feature - name: "QualifyLeadButton" file: "src/entities/lead/ui/QualifyLeadButton.tsx" type: entity - name: "ScoringRulesPage" file: "src/pages/crm/ScoringRulesPage.tsx" type: page api_client: - name: "leadScoringApi" file: "src/entities/lead-scoring/api/lead-scoring.api.ts" methods: - getRules - createRule - updateRule - calculateScore - qualify state_management: - name: "useLeadScoringStore" file: "src/entities/lead-scoring/model/lead-scoring.store.ts" type: zustand database_tables: - schema: sales table: leads file: "database-design/schemas/sales-schema-ddl.sql" operations: - SELECT - UPDATE indices: - idx_leads_score - idx_leads_qualified rls_policy: tenant_isolation_leads tests: backend: unit_tests: - file: "src/modules/crm/services/lead-scoring.service.spec.ts" test_cases: - "should calculate lead score based on rules" - "should qualify lead with high score" - "should auto-assign leads by score threshold" - "should create scoring rule" - "should update score on lead changes" integration_tests: - file: "test/crm/lead-scoring.controller.e2e-spec.ts" test_cases: - "POST /api/v1/crm/scoring-rules should create rule" - "GET /api/v1/crm/scoring-rules should return rules" - "POST /api/v1/crm/leads/:id/calculate-score should calculate" - "POST /api/v1/crm/leads/:id/qualify should qualify lead" - "should enforce tenant isolation" - "should require authentication" - "should check permissions" frontend: component_tests: - file: "src/widgets/scoring-rules-table/ui/ScoringRulesTable.test.tsx" test_cases: - "should render table with rules" - "should handle rule activation" - "should show score breakdown" - file: "src/features/lead-scoring/ui/LeadScoreCard.test.tsx" test_cases: - "should display score with visual indicator" - "should show scoring factors" - "should suggest qualification" e2e_tests: - file: "e2e/crm/lead-scoring.spec.ts" test_cases: - "should calculate lead score" - "should qualify lead manually" - "should auto-qualify by threshold" - "should enforce permissions" acceptance_criteria: - id: AC-001 description: "Sistema calcula score automáticamente basado en reglas" status: Pending test_reference: "test/crm/lead-scoring.controller.e2e-spec.ts:45" - id: AC-002 description: "Leads con score alto se califican automáticamente" status: Pending test_reference: "src/modules/crm/services/lead-scoring.service.spec.ts:82" - id: AC-003 description: "Usuarios pueden ver breakdown de score por factor" status: Pending test_reference: "src/features/lead-scoring/ui/LeadScoreCard.test.tsx:108" business_rules: - id: RN-001 description: "Score se calcula sumando puntos de reglas activas" implementation: "src/modules/crm/services/lead-scoring.service.ts:calculateScore()" test_reference: "src/modules/crm/services/lead-scoring.service.spec.ts:105" - id: RN-002 description: "Lead con score >= 70 se califica como 'hot'" implementation: "src/modules/crm/services/lead-scoring.service.ts:qualifyByScore()" test_reference: "src/modules/crm/services/lead-scoring.service.spec.ts:132" - id: RN-003 description: "Score se recalcula automáticamente al actualizar lead" implementation: "src/modules/crm/services/lead.service.ts:update()" test_reference: "src/modules/crm/services/lead-scoring.service.spec.ts:158" dependencies: rf_dependencies: - RF-MGN-009-001 module_dependencies: - MGN-001 external_dependencies: - name: "@nestjs/common" version: "^10.0.0" - rf_id: RF-MGN-009-005 rf_title: "Conversión a Cotización" rf_file: "requerimientos-funcionales/mgn-009/RF-MGN-009-005-conversión-a-cotización.md" priority: P1 story_points: 5 et_backend: file: "especificaciones-tecnicas/backend/mgn-009/ET-BACKEND-MGN-009-005-conversión-a-cotización.md" endpoints: - method: POST path: /api/v1/crm/opportunities/:id/convert description: "Convertir oportunidad a cotización" - method: POST path: /api/v1/crm/leads/:id/convert description: "Convertir lead a oportunidad" - method: POST path: /api/v1/crm/opportunities/:id/mark-won description: "Marcar oportunidad como ganada" - method: POST path: /api/v1/crm/opportunities/:id/mark-lost description: "Marcar oportunidad como perdida" services: - name: "ConversionService" file: "src/modules/crm/services/conversion.service.ts" methods: - convertToOpportunity - convertToQuotation - markWon - markLost - createPartner controllers: - name: "ConversionController" file: "src/modules/crm/controllers/conversion.controller.ts" dtos: - name: "ConvertLeadDto" file: "src/modules/crm/dto/convert-lead.dto.ts" - name: "ConvertOpportunityDto" file: "src/modules/crm/dto/convert-opportunity.dto.ts" - name: "MarkLostDto" file: "src/modules/crm/dto/mark-lost.dto.ts" et_frontend: file: "especificaciones-tecnicas/frontend/mgn-009/ET-FRONTEND-MGN-009-005-conversión-a-cotización.md" routes: - path: "/crm/leads/:id/convert" component: "ConvertLeadPage" - path: "/crm/opportunities/:id/convert" component: "ConvertOpportunityPage" components: - name: "ConvertLeadButton" file: "src/features/convert-lead/ui/ConvertLeadButton.tsx" type: feature - name: "ConvertOpportunityButton" file: "src/features/convert-opportunity/ui/ConvertOpportunityButton.tsx" type: feature - name: "WonLostButtons" file: "src/widgets/won-lost-buttons/ui/WonLostButtons.tsx" type: widget - name: "ConversionPage" file: "src/pages/crm/ConversionPage.tsx" type: page api_client: - name: "conversionApi" file: "src/entities/conversion/api/conversion.api.ts" methods: - convertLead - convertOpportunity - markWon - markLost state_management: - name: "useConversionStore" file: "src/entities/conversion/model/conversion.store.ts" type: zustand database_tables: - schema: sales table: opportunities file: "database-design/schemas/sales-schema-ddl.sql" operations: - SELECT - INSERT - UPDATE indices: - idx_opportunities_lead_id - idx_opportunities_won_date rls_policy: tenant_isolation_opportunities - schema: sales table: lost_reasons file: "database-design/schemas/sales-schema-ddl.sql" operations: - SELECT indices: - idx_lost_reasons_tenant_id rls_policy: tenant_isolation_lost_reasons tests: backend: unit_tests: - file: "src/modules/crm/services/conversion.service.spec.ts" test_cases: - "should convert lead to opportunity" - "should convert opportunity to quotation" - "should mark opportunity as won and create quotation" - "should mark opportunity as lost with reason" - "should create partner from lead if not exists" integration_tests: - file: "test/crm/conversion.controller.e2e-spec.ts" test_cases: - "POST /api/v1/crm/leads/:id/convert should convert lead" - "POST /api/v1/crm/opportunities/:id/convert should convert opportunity" - "POST /api/v1/crm/opportunities/:id/mark-won should mark won" - "POST /api/v1/crm/opportunities/:id/mark-lost should mark lost" - "should enforce tenant isolation" - "should require authentication" - "should check permissions" frontend: component_tests: - file: "src/features/convert-lead/ui/ConvertLeadButton.test.tsx" test_cases: - "should show conversion dialog" - "should call convert API" - "should redirect to opportunity" - file: "src/widgets/won-lost-buttons/ui/WonLostButtons.test.tsx" test_cases: - "should mark opportunity as won" - "should mark opportunity as lost with reason" - "should show confirmation dialog" e2e_tests: - file: "e2e/crm/conversion.spec.ts" test_cases: - "should convert lead to opportunity" - "should convert opportunity to quotation" - "should mark won and create quotation" - "should enforce permissions" acceptance_criteria: - id: AC-001 description: "Usuario puede convertir lead calificado a oportunidad" status: Pending test_reference: "test/crm/conversion.controller.e2e-spec.ts:42" - id: AC-002 description: "Oportunidad ganada crea automáticamente cotización" status: Pending test_reference: "src/modules/crm/services/conversion.service.spec.ts:78" - id: AC-003 description: "Sistema solicita razón al marcar oportunidad como perdida" status: Pending test_reference: "test/crm/conversion.controller.e2e-spec.ts:115" business_rules: - id: RN-001 description: "Solo leads calificados pueden convertirse a oportunidad" implementation: "src/modules/crm/services/conversion.service.ts:validateLeadQualified()" test_reference: "src/modules/crm/services/conversion.service.spec.ts:102" - id: RN-002 description: "Al marcar como ganada, se crea cotización automáticamente" implementation: "src/modules/crm/services/conversion.service.ts:markWon()" test_reference: "src/modules/crm/services/conversion.service.spec.ts:128" - id: RN-003 description: "Oportunidad perdida debe tener razón registrada" implementation: "src/modules/crm/services/conversion.service.ts:markLost()" test_reference: "src/modules/crm/services/conversion.service.spec.ts:155" dependencies: rf_dependencies: - RF-MGN-009-001 - RF-MGN-009-002 - RF-MGN-007-001 module_dependencies: - MGN-001 - MGN-007 external_dependencies: - name: "@nestjs/common" version: "^10.0.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: 25 total_components: 20 total_tables: 5 total_test_cases: 100 estimated_duration_sprints: 2