api
arquitectura
backend
código
desarrollo
Calmia Nexus - Guía Técnica para Desarrolladores
Versión: 1.0
Última actualización: 2026-02-01
Audiencia: Desarrolladores, Tech Leads, Arquitectos
Índice
Arquitectura General
Estructura de Proyectos
Capas de la Aplicación
Modelos de Datos (Entidades)
DTOs (Data Transfer Objects)
Servicios de Dominio
API REST Endpoints
SignalR Hubs (Real-time)
Entity Framework y DbContext
Patrones y Convenciones
Flujos de Datos Principales
Guía de Integración
Testing
Troubleshooting
1. Arquitectura General
1.1 Diagrama de Arquitectura
graph TB
subgraph "Frontend"
ADMIN[Orchestrator.Admin<br/>Blazor UI]
end
subgraph "Backend"
API[Orchestrator.Api<br/>REST + SignalR]
WORKERS[Orchestrator.Workers<br/>Background Jobs]
MCP[Orchestrator.Mcp<br/>MCP Protocol]
MCP_REMOTE[Orchestrator.Mcp.Remote<br/>Remote MCP Server]
end
subgraph "Shared Libraries"
SHARED_ADMIN[Shared.Admin<br/>Entities, Services, DTOs]
SHARED_EVENTS[Shared.Events<br/>Domain Events]
SHARED_LOGGING[Shared.Logging<br/>Structured Logging]
end
subgraph "External Services"
CLAUDE[Claude API<br/>Anthropic]
CLICKUP[ClickUp API]
SENTRY[Sentry]
WHATSAPP[WhatsApp<br/>UltraMsg]
end
subgraph "Infrastructure"
PG[(PostgreSQL)]
RABBIT[RabbitMQ]
end
ADMIN -->|HTTP/SignalR| API
API --> SHARED_ADMIN
WORKERS --> SHARED_ADMIN
MCP --> SHARED_ADMIN
SHARED_ADMIN --> PG
API -->|SSE/HTTP| CLAUDE
API -->|REST| CLICKUP
API -->|SDK| SENTRY
WORKERS -->|REST| WHATSAPP
API <-->|Pub/Sub| RABBIT
WORKERS <-->|Pub/Sub| RABBIT
1.2 Stack Tecnológico
Capa
Tecnología
Versión
Runtime
.NET
8.0/9.0
Web Framework
ASP.NET Core
8.0
UI Framework
Blazor Server
8.0
UI Components
MudBlazor
7.x
ORM
Entity Framework Core
8.0
Database
PostgreSQL
16.x
Real-time
SignalR
8.0
Messaging
RabbitMQ
3.x
Logging
Serilog
3.x
Error Tracking
Sentry
4.x
1.3 Principios de Diseño
Clean Architecture - Separación de responsabilidades por capas
CQRS Lite - Separación de queries y commands en servicios
Domain-Driven Design - Entidades ricas con lógica de negocio
Multi-Tenancy - Aislamiento por OrganizationId
Event-Driven - Comunicación asíncrona entre componentes
2. Estructura de Proyectos
platform/
├── Orchestrator/
│ └── src/
│ ├── Orchestrator.Api/ # API REST + SignalR
│ │ ├── Controllers/ # Endpoints HTTP
│ │ ├── Hubs/ # SignalR Hubs
│ │ └── Program.cs # Entry point + DI
│ │
│ ├── Orchestrator.Admin/ # Blazor UI
│ │ ├── Components/
│ │ │ ├── Layout/ # MainLayout, EmptyLayout
│ │ │ ├── Pages/ # Páginas Blazor
│ │ │ └── Shared/ # Componentes reutilizables
│ │ ├── Services/ # Servicios de UI
│ │ └── Theme/ # Temas MudBlazor
│ │
│ ├── Orchestrator.Workers/ # Background Jobs
│ │ └── Workers/ # Hosted Services
│ │
│ ├── Orchestrator.Mcp/ # MCP Protocol
│ └── Orchestrator.Mcp.Remote/ # Remote MCP Server
│
├── Shared/
│ ├── Shared.Admin/
│ │ ├── Data/
│ │ │ ├── NexusDbContext.cs # DbContext principal
│ │ │ ├── NexusDbContextFactory.cs
│ │ │ └── Migrations/ # EF Migrations
│ │ ├── Dtos/ # Data Transfer Objects
│ │ ├── Entities/ # Modelos de dominio
│ │ └── Services/ # Servicios de dominio
│ │
│ ├── Shared.Events/ # Eventos de dominio
│ └── Shared.Logging/ # Logging estructurado
│
├── Messaging/
│ └── WhatsappService/ # Servicio de WhatsApp
│
└── Tools/
├── NexusRemoteAgent/ # Agente remoto CLI
└── TunnelClient/ # Cliente de túnel
2.1 Dependencias entre Proyectos
graph LR
API[Orchestrator.Api] --> SA[Shared.Admin]
ADMIN[Orchestrator.Admin] --> SA
WORKERS[Orchestrator.Workers] --> SA
MCP[Orchestrator.Mcp] --> SA
SA --> SE[Shared.Events]
SA --> SL[Shared.Logging]
WHATSAPP[WhatsappService] --> SE
3. Capas de la Aplicación
3.1 Capa de Presentación (Orchestrator.Admin)
Components/
├── Layout/
│ ├── MainLayout.razor # Layout principal con sidebar
│ └── EmptyLayout.razor # Layout minimalista
│
├── Pages/
│ ├── Workspace/
│ │ ├── Workspace.razor # Centro de trabajo principal
│ │ ├── ConversationArea.razor # Área de chat
│ │ ├── MessageInput.razor # Input de mensajes
│ │ ├── ContextPanel.razor # Panel de contexto
│ │ ├── BacklogMiniPanel.razor # Panel de backlog
│ │ └── MemoriesPanel.razor # Panel de memorias
│ │
│ ├── Agents/
│ │ ├── AgentList.razor # Lista de agentes
│ │ └── AgentDetail.razor # Detalle de agente
│ │
│ ├── Projects/
│ │ ├── ProjectList.razor # Lista de proyectos
│ │ └── ProjectDetail.razor # Detalle de proyecto
│ │
│ └── Dashboard/
│ └── Dashboard.razor # Dashboard principal
│
└── Shared/
├── OrganizationProjectSelector.razor
├── ThemeSwitcher.razor
└── ConfirmDialog.razor
3.2 Capa de API (Orchestrator.Api)
Controllers/
├── BacklogController.cs # /api/backlog
├── DevSessionsController.cs # /api/dev-sessions
├── CopilotController.cs # /api/copilot
├── AIController.cs # /api/ai
├── ProjectsController.cs # /api/projects
├── AgentsController.cs # /api/agents
├── ExecutionsController.cs # /api/executions
└── ... (30+ controllers)
Hubs/
├── AdminHub.cs # Dashboard + Workspace
├── RemoteAgentHub.cs # Agentes remotos
└── TunnelHub.cs # Túneles SSH
3.3 Capa de Servicios (Shared.Admin/Services)
Services/
├── Core/
│ ├── DevSessionService.cs # Sesiones interactivas
│ ├── CopilotService.cs # Copiloto autónomo
│ ├── BacklogService.cs # Gestión de backlog
│ └── ExecutionService.cs # Ciclo de ejecuciones
│
├── AI/
│ ├── AIProviderService.cs # Proveedores de IA
│ ├── AISuggestionService.cs # Sugerencias IA
│ ├── CopilotPlanGenerator.cs # Generación de planes
│ └── BacklogAIProcessorService.cs # Análisis de backlog
│
├── Integration/
│ ├── ClickUpSyncService.cs # Sincronización ClickUp
│ ├── SentryService.cs # Integración Sentry
│ └── RemoteAgentService.cs # Agentes remotos
│
└── Support/
├── MemoryService.cs # Memoria de usuario
├── HistoryManagerService.cs # Optimización historial
└── WorkspaceEnrichmentService.cs # Enriquecimiento
3.4 Capa de Datos (Shared.Admin/Data)
Data/
├── NexusDbContext.cs # 64+ DbSets
├── NexusDbContextFactory.cs # Design-time factory
├── NexusDbSeeder.cs # Datos iniciales
└── Migrations/ # 19+ migrations
4. Modelos de Datos (Entidades)
4.1 Entidades Principales
Organization
public class Organization
{
public Guid Id { get ; set ; }
public required string Slug { get ; set ; } // Único
public required string Name { get ; set ; }
public string? Description { get ; set ; }
public string? LogoUrl { get ; set ; }
public string? ThemeId { get ; set ; }
public string? PrimaryColor { get ; set ; }
public bool IsActive { get ; set ; } = true ;
public DateTime CreatedAt { get ; set ; }
public DateTime ? UpdatedAt { get ; set ; }
// Navegación
public List < Project > Projects { get ; set ; } = [];
public List < CommunicationChannel > CommunicationChannels { get ; set ; } = [];
}
Project
public class Project
{
public Guid Id { get ; set ; }
public required string Slug { get ; set ; } // Único
public required string Name { get ; set ; }
public string? Description { get ; set ; }
public string? RepoPath { get ; set ; }
public string? Instructions { get ; set ; } // Inyectables en sesiones
// Configuración Sentry
public string? SentryConfigJson { get ; set ; } // JSONB
// Configuración de documentos
public bool EnableDocumentContextualization { get ; set ; }
public int MaxDocumentsPerRequest { get ; set ; } = 3 ;
public double MinDocumentRelevance { get ; set ; } = 0.3 ;
// Navegación
public List < ProjectSecret > Secrets { get ; set ; } = [];
public List < ScheduledTask > Tasks { get ; set ; } = [];
public List < Execution > Executions { get ; set ; } = [];
public List < BacklogItem > BacklogItems { get ; set ; } = [];
}
DevSession
public class DevSession
{
public Guid Id { get ; set ; }
public required string Name { get ; set ; }
public string? Description { get ; set ; }
// Referencias
public Guid ? ProjectId { get ; set ; }
public string? AgentId { get ; set ; }
public string? PluginId { get ; set ; }
public Guid ? RemoteAgentId { get ; set ; }
public Guid UserId { get ; set ; }
public Guid OrganizationId { get ; set ; }
public Guid ? BacklogItemId { get ; set ; } // Tarea origen
// Estado
public string Status { get ; set ; } = "active" ; // active, completed, archived
public int MessageCount { get ; set ; }
public int TokensUsed { get ; set ; }
// Instrucciones
public string? SessionInstructions { get ; set ; }
public bool InjectSessionInstructions { get ; set ; } = true ;
// Optimización de historial
public int MaxHistoryMessages { get ; set ; } = 20 ;
public int MaxHistoryTokens { get ; set ; } = 50000 ;
public bool AutoSummarizeHistory { get ; set ; } = true ;
public string? HistorySummary { get ; set ; }
public DateTime ? HistorySummaryGeneratedAt { get ; set ; }
public int? HistorySummaryMessageCount { get ; set ; }
// Work tracking
public DateTime ? WorkStartedAt { get ; set ; }
public DateTime ? WorkEndedAt { get ; set ; }
// Navegación
public List < DevSessionMessage > Messages { get ; set ; } = [];
}
CopilotExecution
public class CopilotExecution
{
public Guid Id { get ; set ; }
public Guid DevSessionId { get ; set ; }
public required string Objective { get ; set ; }
// Estado (máquina de estados)
public CopilotState State { get ; set ; } = CopilotState . Idle ;
public CopilotResult Result { get ; set ; } = CopilotResult . Pending ;
// Progreso
public int TotalSteps { get ; set ; }
public int CurrentStepIndex { get ; set ; }
public int CompletedSteps { get ; set ; }
public int FailedSteps { get ; set ; }
// Configuración
public bool AutoApprove { get ; set ; } = false ;
public bool AutoContinue { get ; set ; } = true ;
public bool PauseOnWarning { get ; set ; } = false ;
public int MaxIterations { get ; set ; } = 10 ;
public int MaxRetries { get ; set ; } = 3 ;
public int TimeoutMinutes { get ; set ; } = 30 ;
public int StepTimeoutSeconds { get ; set ; } = 300 ;
// Métricas
public int TotalIterations { get ; set ; }
public int TotalTokensUsed { get ; set ; }
public int TotalMessagesGenerated { get ; set ; }
public long? TotalDurationMs { get ; set ; }
// Batch processing
public Guid ? BatchId { get ; set ; }
// Datos adicionales (JSONB)
public string? ArtifactsJson { get ; set ; }
public string? WarningsJson { get ; set ; }
// Navegación
public List < CopilotStep > Steps { get ; set ; } = [];
public DevSession ? DevSession { get ; set ; }
}
public enum CopilotState
{
Idle ,
Planning ,
AwaitingApproval ,
Executing ,
Paused ,
Completed ,
Failed ,
Cancelled
}
public enum CopilotResult
{
Pending ,
Success ,
Warning ,
Failed
}
4.2 Diagrama de Relaciones
erDiagram
Organization ||--o{ Project : has
Organization ||--o{ User : "members via"
Organization ||--o{ DevSession : contains
Project ||--o{ ScheduledTask : has
Project ||--o{ Execution : has
Project ||--o{ BacklogItem : has
Project ||--o{ ProjectSecret : has
Project |o--o| ClickUpConfig : "syncs with"
User ||--o{ DevSession : creates
User ||--o{ UserMemory : has
User ||--o{ WellnessRecord : tracks
DevSession ||--o{ DevSessionMessage : contains
DevSession ||--o{ CopilotExecution : "can have"
DevSession |o--|| BacklogItem : "originates from"
CopilotExecution ||--o{ CopilotStep : has
CopilotExecution |o--|| CopilotBatchExecution : "part of"
ScheduledTask ||--o{ Execution : generates
Execution ||--o{ BacklogItem : creates
Agent ||--o{ AgentSkill : has
Agent ||--o{ AgentTrigger : has
Plugin ||--o{ PluginAgent : "includes"
Plugin ||--o{ PluginSkill : "includes"
Plugin ||--o{ PluginCommand : "includes"
Plugin ||--o{ PluginProcedure : "includes"
5. DTOs (Data Transfer Objects)
5.1 Convención de Nombres
Sufijo
Propósito
Ejemplo
Dto
Transferencia general
BacklogItemDto
ListDto
Listados (menos campos)
BacklogItemListDto
DetailDto
Detalle (todos los campos)
BacklogItemDetailDto
CreateRequest
Creación
CreateBacklogItemRequest
UpdateRequest
Actualización
UpdateBacklogItemRequest
Response
Respuesta de API
BacklogListResponse
5.2 DTOs Principales
BacklogDtos.cs
// Para listados rápidos (mini panel)
public record BacklogQuickItemDto (
Guid Id ,
string Title ,
string Status ,
string? Priority ,
string? ItemType ,
DateTime CreatedAt
);
// Para listados completos
public record BacklogItemListDto (
Guid Id ,
string Title ,
string Status ,
string? Priority ,
string? ItemType ,
string? AICategory ,
string? EstimatedEffort ,
DateTime CreatedAt ,
DateTime ? UpdatedAt
);
// Para detalle
public record BacklogItemDetailDto (
Guid Id ,
string Title ,
string? Description ,
string Status ,
string? Priority ,
string? ItemType ,
string? AICategory ,
string? AISummary ,
string? EstimatedEffort ,
string? Source ,
string? SentryIssueId ,
string? SentryPermalink ,
string? ClickUpTaskId ,
string? ClickUpUrl ,
Guid ? ProjectId ,
Guid ? ExecutionId ,
Guid ? ConvertedToTaskId ,
DateTime CreatedAt ,
DateTime ? UpdatedAt ,
DateTime ? ResolvedAt
);
// Para crear
public record CreateBacklogItemRequest (
string Title ,
string? Description ,
string? ItemType ,
string? Priority ,
Guid ProjectId
);
// Para convertir a tarea
public record ConvertToTaskRequest (
Guid BacklogItemId ,
string? AgentId ,
string? ProcedureId ,
bool ExecuteImmediately = false
);
DevSessionDtos.cs
public record DevSessionListDto (
Guid Id ,
string Name ,
string Status ,
int MessageCount ,
int TokensUsed ,
string? AgentId ,
Guid ? ProjectId ,
DateTime CreatedAt ,
DateTime ? UpdatedAt
);
public record DevSessionDetailDto (
Guid Id ,
string Name ,
string? Description ,
string Status ,
int MessageCount ,
int TokensUsed ,
string? AgentId ,
string? PluginId ,
Guid ? ProjectId ,
Guid ? RemoteAgentId ,
Guid ? BacklogItemId ,
string? SessionInstructions ,
bool InjectSessionInstructions ,
int MaxHistoryMessages ,
bool AutoSummarizeHistory ,
DateTime CreatedAt ,
DateTime ? UpdatedAt ,
List < DevSessionMessageDto > Messages
);
public record CreateDevSessionRequest (
string Name ,
string? Description ,
Guid ? ProjectId ,
string? AgentId ,
string? PluginId ,
Guid ? BacklogItemId
);
public record DevSessionSendMessageRequest (
string Content ,
string? Model = null ,
bool Stream = true
);
CopilotDtos.cs
public record CopilotExecutionDto (
Guid Id ,
Guid DevSessionId ,
string Objective ,
string State ,
string Result ,
int TotalSteps ,
int CurrentStepIndex ,
int CompletedSteps ,
int FailedSteps ,
bool AutoApprove ,
bool AutoContinue ,
int TotalTokensUsed ,
long? TotalDurationMs ,
DateTime CreatedAt ,
DateTime ? CompletedAt ,
List < CopilotStepDto > Steps
);
public record CopilotStepDto (
Guid Id ,
int Order ,
string Title ,
string? Description ,
string Status ,
string? Category ,
int? ResponseTokens ,
int RetryCount ,
long? DurationMs ,
DateTime CreatedAt ,
DateTime ? CompletedAt
);
public record StartCopilotRequest (
string Objective ,
bool AutoApprove = false ,
bool AutoContinue = true ,
int MaxIterations = 10 ,
int TimeoutMinutes = 30
);
public record ModifyPlanRequest (
string Instructions
);
6. Servicios de Dominio
6.1 Servicios Principales
DevSessionService
public interface IDevSessionService
{
Task < DevSession > CreateSessionAsync ( CreateDevSessionRequest request , Guid userId , Guid organizationId );
Task < DevSession ?> GetSessionAsync ( Guid sessionId );
Task < List < DevSessionListDto >> GetSessionsAsync ( Guid userId , Guid ? projectId = null );
Task < DevSessionMessage > SendMessageAsync ( Guid sessionId , string content , string? model = null );
Task SendMessageStreamAsync ( Guid sessionId , string content , string? model , Func < string , Task > onChunk , CancellationToken ct = default );
Task < DevSession > UpdateSessionAsync ( Guid sessionId , UpdateDevSessionRequest request );
Task DeleteSessionAsync ( Guid sessionId );
}
// Implementación con optimización de historial
public class DevSessionService : IDevSessionService
{
private readonly NexusDbContext _db ;
private readonly IAIProviderService _aiProvider ;
private readonly IHistoryManagerService ? _historyManager ;
private readonly ConcurrentDictionary < Guid , SemaphoreSlim > _sessionLocks = new ();
public async Task SendMessageStreamAsync (
Guid sessionId ,
string content ,
string? model ,
Func < string , Task > onChunk ,
CancellationToken ct = default )
{
// Obtener lock por sesión
var sessionLock = _sessionLocks . GetOrAdd ( sessionId , _ => new SemaphoreSlim ( 1 , 1 ));
await sessionLock . WaitAsync ( ct );
try
{
var session = await _db . DevSessions
. Include ( s => s . Messages . OrderBy ( m => m . CreatedAt ))
. FirstOrDefaultAsync ( s => s . Id == sessionId , ct );
if ( session == null ) throw new NotFoundException ( "Session not found" );
// Optimizar historial si está habilitado
List < DevSessionMessage > historyMessages ;
string? historySummary = null ;
if ( _historyManager != null && session . AutoSummarizeHistory )
{
var optimizedHistory = await _historyManager . GetOptimizedHistoryAsync ( sessionId );
historyMessages = optimizedHistory . RecentMessages ;
historySummary = optimizedHistory . Summary ;
}
else
{
historyMessages = session . Messages . TakeLast ( session . MaxHistoryMessages ). ToList ();
}
// Construir contexto
var messages = BuildConversationMessages ( historyMessages , historySummary , session );
messages . Add ( new { role = "user" , content });
// Stream response desde Claude
await _aiProvider . StreamCompletionAsync (
model ?? "claude-sonnet-4-20250514" ,
messages ,
async chunk => await onChunk ( chunk ),
ct
);
// Guardar mensajes
await SaveMessagesAsync ( session , content , fullResponse , model );
}
finally
{
sessionLock . Release ();
}
}
}
CopilotService
public interface ICopilotService
{
Task < CopilotExecution > StartAsync ( Guid sessionId , StartCopilotRequest request );
Task < CopilotExecution > PauseAsync ( Guid executionId );
Task < CopilotExecution > ResumeAsync ( Guid executionId );
Task < CopilotExecution > CancelAsync ( Guid executionId );
Task < CopilotExecution > ApprovePlanAsync ( Guid executionId );
Task < CopilotExecution > ModifyPlanAsync ( Guid executionId , ModifyPlanRequest request );
Task < CopilotExecution > SkipStepAsync ( Guid executionId , int stepIndex );
}
// Máquina de estados del Copiloto
public class CopilotService : ICopilotService
{
// Estados válidos
// Idle -> Planning -> AwaitingApproval -> Executing -> Completed/Failed
// | |
// v v
// Cancelled Paused -> Executing
private readonly SemaphoreSlim _stateLock = new ( 1 , 1 );
public async Task < CopilotExecution > StartAsync ( Guid sessionId , StartCopilotRequest request )
{
await _stateLock . WaitAsync ();
try
{
// 1. Crear ejecución
var execution = new CopilotExecution
{
DevSessionId = sessionId ,
Objective = request . Objective ,
State = CopilotState . Planning ,
AutoApprove = request . AutoApprove ,
AutoContinue = request . AutoContinue ,
MaxIterations = request . MaxIterations ,
TimeoutMinutes = request . TimeoutMinutes
};
_db . CopilotExecutions . Add ( execution );
await _db . SaveChangesAsync ();
// 2. Generar plan
var plan = await _planGenerator . GeneratePlanAsync (
execution . Objective ,
sessionId
);
// 3. Crear steps
foreach ( var ( step , index ) in plan . Steps . Select (( s , i ) => ( s , i )))
{
execution . Steps . Add ( new CopilotStep
{
ExecutionId = execution . Id ,
Order = index ,
Title = step . Title ,
Description = step . Description ,
Status = CopilotStepStatus . Pending
});
}
execution . TotalSteps = execution . Steps . Count ;
// 4. Transición de estado
if ( request . AutoApprove )
{
execution . State = CopilotState . Executing ;
_ = Task . Run (() => ExecuteStepsAsync ( execution . Id ));
}
else
{
execution . State = CopilotState . AwaitingApproval ;
}
await _db . SaveChangesAsync ();
// 5. Notificar
await _adminHub . Clients . Group ( $"session-{sessionId}" )
. SendAsync ( "CopilotStateChanged" , new CopilotStateMessage ( execution ));
return execution ;
}
finally
{
_stateLock . Release ();
}
}
}
BacklogService
public interface IBacklogService
{
Task < BacklogItem > CreateAsync ( CreateBacklogItemRequest request , Guid userId );
Task < BacklogItem ?> GetAsync ( Guid id );
Task < BacklogListResponse > GetListAsync ( BacklogListRequest request );
Task < List < BacklogQuickItemDto >> GetQuickListAsync ( Guid projectId , int limit = 10 );
Task < BacklogItem > UpdateAsync ( Guid id , UpdateBacklogItemRequest request );
Task DeleteAsync ( Guid id );
Task < ScheduledTask > ConvertToTaskAsync ( ConvertToTaskRequest request );
Task SyncWithClickUpAsync ( Guid projectId );
}
public class BacklogService : IBacklogService
{
public async Task < ScheduledTask > ConvertToTaskAsync ( ConvertToTaskRequest request )
{
var backlogItem = await _db . BacklogItems
. FirstOrDefaultAsync ( b => b . Id == request . BacklogItemId )
?? throw new NotFoundException ( "Backlog item not found" );
// Crear tarea programada
var task = new ScheduledTask
{
Slug = $"backlog-{backlogItem.Id:N}" . Substring ( 0 , 20 ),
Name = backlogItem . Title ,
TaskType = "copilot" ,
AgentId = request . AgentId ,
ProcedureId = request . ProcedureId ,
ProjectId = backlogItem . ProjectId ,
SourceBacklogItemId = backlogItem . Id ,
Priority = backlogItem . Priority ?? "medium" ,
IsOneTimeExecution = true
};
_db . ScheduledTasks . Add ( task );
backlogItem . ConvertedToTaskId = task . Id ;
backlogItem . Status = "in_progress" ;
await _db . SaveChangesAsync ();
// Ejecutar inmediatamente si se solicita
if ( request . ExecuteImmediately )
{
await _executionService . TriggerAsync ( task . Id );
}
return task ;
}
}
6.2 Servicios de Integración
ClickUpSyncService
public interface IClickUpSyncService
{
Task < ClickUpSyncResult > SyncAsync ( Guid projectId );
Task < string? > CreateInClickUpAsync ( BacklogItem item );
Task UpdateInClickUpAsync ( BacklogItem item );
Task < List < ClickUpTask >> PullFromClickUpAsync ( Guid projectId );
}
public class ClickUpSyncService : IClickUpSyncService
{
private readonly HttpClient _httpClient ;
private readonly NexusDbContext _db ;
public async Task < string? > CreateInClickUpAsync ( BacklogItem item )
{
var config = await GetConfigAsync ( item . ProjectId );
if ( config == null || ! config . SyncEnabled ) return null ;
var payload = new
{
name = item . Title ,
description = item . Description ,
status = MapStatusToClickUp ( item . Status , config ),
priority = MapPriorityToClickUp ( item . Priority )
};
var response = await _httpClient . PostAsJsonAsync (
$"https://api.clickup.com/api/v2/list/{config.ClickUpListId}/task" ,
payload
);
// Log sync operation
await LogSyncAsync ( item . Id , item . ProjectId , "create" , "push" , response );
if ( response . IsSuccessStatusCode )
{
var result = await response . Content . ReadFromJsonAsync < ClickUpTaskResponse > ();
return result ?. Id ;
}
return null ;
}
}
6.3 Diagrama de Dependencias
graph TB
subgraph "API Layer"
BC[BacklogController]
DC[DevSessionsController]
CC[CopilotController]
end
subgraph "Service Layer"
BS[BacklogService]
DS[DevSessionService]
CS[CopilotService]
HMS[HistoryManagerService]
AIS[AISuggestionService]
CUS[ClickUpSyncService]
end
subgraph "Data Layer"
DB[(NexusDbContext)]
end
subgraph "External"
CLAUDE[Claude API]
CLICKUP[ClickUp API]
end
BC --> BS
DC --> DS
CC --> CS
BS --> DB
BS --> CUS
BS --> AIS
DS --> DB
DS --> HMS
DS --> CLAUDE
CS --> DB
CS --> DS
CUS --> CLICKUP
7. API REST Endpoints
7.1 Endpoints Principales
Backlog API
# Listar items (con filtros y paginación)
GET /api/backlog?projectId={guid}&status={status}&page={int}&pageSize={int}
# Obtener item rápido (para mini panel)
GET /api/backlog/quick?projectId={guid}&limit={int}
# Obtener detalle
GET /api/backlog/{id}
# Crear item
POST /api/backlog
Content-Type: application/json
{
"title": "Implementar feature X",
"description": "Descripción detallada...",
"itemType": "feature",
"priority": "high",
"projectId": "guid"
}
# Actualizar item
PUT /api/backlog/{id}
Content-Type: application/json
{
"title": "Título actualizado",
"status": "in_progress"
}
# Convertir a tarea
POST /api/backlog/{id}/convert-to-task
Content-Type: application/json
{
"agentId": "code-assistant",
"procedureId": "implement-feature",
"executeImmediately": true
}
# Eliminar item
DELETE /api/backlog/{id}
DevSessions API
# Listar sesiones
GET /api/dev-sessions?projectId={guid}&status={status}
# Obtener sesión con mensajes
GET /api/dev-sessions/{id}
# Crear sesión
POST /api/dev-sessions
Content-Type: application/json
{
"name": "Feature X Development",
"projectId": "guid",
"agentId": "code-assistant",
"backlogItemId": "guid"
}
# Enviar mensaje (streaming)
POST /api/dev-sessions/{id}/messages
Content-Type: application/json
{
"content": "Implementa la función de login",
"model": "claude-sonnet-4-20250514",
"stream": true
}
# Respuesta streaming (SSE)
HTTP / 1.1 200 OK
Content-Type : text/event-stream
data: {"chunk": "Voy a implementar"}
data: {"chunk": " la función de login"}
data: {"chunk": " usando JWT..."}
data: [DONE]
# Actualizar sesión
PUT /api/dev-sessions/{id}
Content-Type: application/json
{
"name": "Nuevo nombre",
"sessionInstructions": "Instrucciones específicas..."
}
Copilot API
# Iniciar copiloto
POST /api/copilot/start
Content-Type: application/json
{
"sessionId": "guid",
"objective": "Implementar sistema de autenticación completo",
"autoApprove": false,
"maxIterations": 10,
"timeoutMinutes": 30
}
# Obtener estado
GET /api/copilot/{executionId}
# Aprobar plan
POST /api/copilot/{executionId}/approve
# Modificar plan
POST /api/copilot/{executionId}/modify
Content-Type: application/json
{
"instructions": "Añade un paso para validación de email"
}
# Pausar ejecución
POST /api/copilot/{executionId}/pause
# Reanudar ejecución
POST /api/copilot/{executionId}/resume
# Cancelar ejecución
POST /api/copilot/{executionId}/cancel
# Saltar paso
POST /api/copilot/{executionId}/skip/{stepIndex}
7.2 Tabla de Endpoints
Controller
Ruta Base
Métodos
Descripción
BacklogController
/api/backlog
GET, POST, PUT, DELETE
Gestión de backlog
DevSessionsController
/api/dev-sessions
GET, POST, PUT, DELETE, POST/messages
Sesiones de desarrollo
CopilotController
/api/copilot
POST, GET, POST/*
Copiloto autónomo
CopilotBatchController
/api/copilot-batch
POST, GET, POST/*
Batch processing
AIController
/api/ai
POST
Servicios de IA
ProjectsController
/api/projects
CRUD
Gestión de proyectos
AgentsController
/api/agents
CRUD
Gestión de agentes
ExecutionsController
/api/executions
GET, POST
Historial de ejecuciones
TasksController
/api/tasks
CRUD
Tareas programadas
RemoteAgentsController
/api/remote-agents
CRUD
Agentes remotos
MemoriesController
/api/memories
CRUD
Memoria de usuario
ContextSnippetsController
/api/context-snippets
CRUD
Fragmentos de contexto
8. SignalR Hubs (Real-time)
8.1 AdminHub
// Hub para comunicación con dashboard y workspace
[Authorize]
public class AdminHub : Hub
{
// Grupos disponibles
// - dashboard: Suscriptores del dashboard
// - execution-{id}: Suscriptores a ejecución específica
// - session-{id}: Suscriptores a sesión específica
// - user-{id}: Notificaciones por usuario
// Cliente -> Servidor
public async Task SubscribeToDashboard ()
{
await Groups . AddToGroupAsync ( Context . ConnectionId , "dashboard" );
}
public async Task SubscribeToSession ( string sessionId )
{
await Groups . AddToGroupAsync ( Context . ConnectionId , $"session-{sessionId}" );
}
public async Task UnsubscribeFromSession ( string sessionId )
{
await Groups . RemoveFromGroupAsync ( Context . ConnectionId , $"session-{sessionId}" );
}
// Servidor -> Cliente (llamados desde servicios)
// ExecutionUpdated, CopilotStateChanged, NotificationReceived, etc.
}
// Mensajes tipados
public record ExecutionUpdateMessage (
Guid ExecutionId ,
string Status ,
string? Output ,
DateTime Timestamp
);
public record CopilotStateMessage (
Guid ExecutionId ,
string State ,
int CurrentStep ,
int TotalSteps ,
string? CurrentStepTitle
);
8.2 RemoteAgentHub
// Hub para comunicación con agentes remotos (Claude CLI)
public class RemoteAgentHub : Hub
{
private readonly ConcurrentDictionary < string , Guid > _connectionToAgent = new ();
private readonly ConcurrentDictionary < Guid , string > _agentToConnection = new ();
private readonly ConcurrentDictionary < Guid , SemaphoreSlim > _commandLocks = new ();
// Agente -> Servidor
public async Task RegisterAgent ( string apiKey , string systemInfo , string agentVersion )
{
var agent = await _db . RemoteAgents
. FirstOrDefaultAsync ( a => a . ApiKeyHash == HashApiKey ( apiKey ));
if ( agent == null )
{
Context . Abort ();
return ;
}
agent . ConnectionId = Context . ConnectionId ;
agent . Status = "Online" ;
agent . SystemInfo = systemInfo ;
agent . AgentVersion = agentVersion ;
_connectionToAgent [ Context . ConnectionId ] = agent . Id ;
_agentToConnection [ agent . Id ] = Context . ConnectionId ;
await _db . SaveChangesAsync ();
}
public async Task SendChunks ( Guid commandId , int sequence , List < ChunkData > chunks )
{
// Procesar chunks de respuesta
}
public async Task CommandCompleted ( Guid commandId , RemoteCommandResult result )
{
var command = await _db . RemoteAgentCommands . FindAsync ( commandId );
if ( command != null )
{
command . Status = "Completed" ;
command . Result = result . Output ;
await _db . SaveChangesAsync ();
}
}
// Servidor -> Agente
public async Task ExecuteCommandAsync ( Guid agentId , Guid commandId , string prompt , string workingDirectory )
{
if ( _agentToConnection . TryGetValue ( agentId , out var connectionId ))
{
await Clients . Client ( connectionId ). SendAsync (
"ExecuteCommand" ,
commandId ,
prompt ,
workingDirectory
);
}
}
}
8.3 Uso desde Blazor
// En el componente Workspace.razor
@inject SignalRService SignalR
@code {
private HubConnection ? _hubConnection ;
protected override async Task OnInitializedAsync ()
{
_hubConnection = new HubConnectionBuilder ()
. WithUrl ( NavigationManager . ToAbsoluteUri ( "/hubs/admin" ))
. WithAutomaticReconnect ()
. Build ();
// Suscribirse a eventos
_hubConnection . On < CopilotStateMessage > ( "CopilotStateChanged" , message =>
{
// Actualizar UI
_copilotState = message . State ;
_currentStep = message . CurrentStep ;
StateHasChanged ();
});
await _hubConnection . StartAsync ();
await _hubConnection . InvokeAsync ( "SubscribeToSession" , SessionId . ToString ());
}
public async ValueTask DisposeAsync ()
{
if ( _hubConnection != null )
{
await _hubConnection . InvokeAsync ( "UnsubscribeFromSession" , SessionId . ToString ());
await _hubConnection . DisposeAsync ();
}
}
}
9. Entity Framework y DbContext
9.1 Configuración del DbContext
public class NexusDbContext : DbContext
{
// 64+ DbSets
public DbSet < Organization > Organizations => Set < Organization > ();
public DbSet < Project > Projects => Set < Project > ();
public DbSet < User > Users => Set < User > ();
public DbSet < DevSession > DevSessions => Set < DevSession > ();
public DbSet < DevSessionMessage > DevSessionMessages => Set < DevSessionMessage > ();
public DbSet < CopilotExecution > CopilotExecutions => Set < CopilotExecution > ();
public DbSet < CopilotStep > CopilotSteps => Set < CopilotStep > ();
public DbSet < BacklogItem > BacklogItems => Set < BacklogItem > ();
public DbSet < ScheduledTask > ScheduledTasks => Set < ScheduledTask > ();
public DbSet < Execution > Executions => Set < Execution > ();
public DbSet < Agent > Agents => Set < Agent > ();
public DbSet < RemoteAgent > RemoteAgents => Set < RemoteAgent > ();
// ... más DbSets
protected override void OnModelCreating ( ModelBuilder modelBuilder )
{
// Índices únicos
modelBuilder . Entity < Organization > ()
. HasIndex ( o => o . Slug )
. IsUnique ();
modelBuilder . Entity < Project > ()
. HasIndex ( p => p . Slug )
. IsUnique ();
// Índices compuestos para queries frecuentes
modelBuilder . Entity < BacklogItem > ()
. HasIndex ( b => new { b . ProjectId , b . Status });
modelBuilder . Entity < DevSession > ()
. HasIndex ( d => new { d . UserId , d . Status });
modelBuilder . Entity < Execution > ()
. HasIndex ( e => new { e . ProjectId , e . Status });
// Configuración de JSONB (PostgreSQL)
modelBuilder . Entity < CopilotExecution > ()
. Property ( c => c . ArtifactsJson )
. HasColumnType ( "jsonb" );
modelBuilder . Entity < BacklogItem > ()
. Property ( b => b . MetadataJson )
. HasColumnType ( "jsonb" );
// Relaciones y delete behaviors
modelBuilder . Entity < DevSession > ()
. HasMany ( d => d . Messages )
. WithOne ( m => m . Session )
. HasForeignKey ( m => m . SessionId )
. OnDelete ( DeleteBehavior . Cascade );
modelBuilder . Entity < CopilotExecution > ()
. HasMany ( c => c . Steps )
. WithOne ( s => s . Execution )
. HasForeignKey ( s => s . ExecutionId )
. OnDelete ( DeleteBehavior . Cascade );
}
}
9.2 Migraciones
# Crear nueva migración
cd Orchestrator/src/Orchestrator.Api
dotnet ef migrations add NombreMigracion --project ../../Shared/Shared.Admin
# Aplicar migraciones
dotnet ef database update --project ../../Shared/Shared.Admin
# Generar script SQL
dotnet ef migrations script --project ../../Shared/Shared.Admin --idempotent
9.3 Patrones de Acceso a Datos
// ✅ Patrón correcto: Crear scope para operaciones background
public class CopilotService
{
private readonly IServiceScopeFactory _scopeFactory ;
public async Task ExecuteStepsAsync ( Guid executionId )
{
using var scope = _scopeFactory . CreateScope ();
var db = scope . ServiceProvider . GetRequiredService < NexusDbContext > ();
// Ahora podemos usar db de forma segura en background
var execution = await db . CopilotExecutions
. Include ( e => e . Steps )
. FirstOrDefaultAsync ( e => e . Id == executionId );
// ... procesamiento
}
}
// ✅ Patrón correcto: Proyección para listados
public async Task < List < BacklogItemListDto >> GetListAsync ()
{
return await _db . BacklogItems
. Where ( b => b . ProjectId == projectId )
. OrderByDescending ( b => b . CreatedAt )
. Select ( b => new BacklogItemListDto (
b . Id ,
b . Title ,
b . Status ,
b . Priority ,
b . ItemType ,
b . AICategory ,
b . EstimatedEffort ,
b . CreatedAt ,
b . UpdatedAt
))
. ToListAsync ();
}
// ❌ Evitar: Cargar entidad completa para listados
// return await _db.BacklogItems.ToListAsync();
10. Patrones y Convenciones
10.1 Estructura de Archivos
Feature/
├── FeatureController.cs # Endpoints HTTP
├── FeatureService.cs # Lógica de negocio
├── FeatureDtos.cs # DTOs
├── Feature.cs # Entidad (en Entities/)
└── FeatureTests.cs # Tests (en Tests/)
10.2 Convenciones de Código
// Servicios: Sufijo "Service"
public interface IBacklogService { }
public class BacklogService : IBacklogService { }
// DTOs: Sufijo "Dto", "Request", "Response"
public record BacklogItemDto { }
public record CreateBacklogItemRequest { }
public record BacklogListResponse { }
// Entidades: Sin sufijo, PascalCase
public class BacklogItem { }
public class DevSession { }
// Controllers: Sufijo "Controller"
[ApiController]
[Route("api/[controller] ")]
public class BacklogController : ControllerBase { }
// Métodos async: Sufijo "Async"
public async Task < BacklogItem > CreateAsync (...)
public async Task DeleteAsync ( Guid id )
// Métodos de streaming: Sufijo "StreamAsync"
public async Task SendMessageStreamAsync (...)
10.3 Inyección de Dependencias
// Program.cs - Registrar servicios
builder . Services . AddScoped < IBacklogService , BacklogService > ();
builder . Services . AddScoped < IDevSessionService , DevSessionService > ();
builder . Services . AddScoped < ICopilotService , CopilotService > ();
// Servicios que necesitan HttpClient
builder . Services . AddHttpClient < IClickUpSyncService , ClickUpSyncService > ( client =>
{
client . DefaultRequestHeaders . Add ( "Authorization" , "Bearer ..." );
});
// Servicios singleton (cuidado con DbContext)
builder . Services . AddSingleton < IMemoryCache , MemoryCache > ();
// Hosted services para background jobs
builder . Services . AddHostedService < MetricsCollectorWorker > ();
10.4 Manejo de Errores
// Excepciones personalizadas
public class NotFoundException : Exception
{
public NotFoundException ( string message ) : base ( message ) { }
}
public class ValidationException : Exception
{
public Dictionary < string , string [] > Errors { get ; }
public ValidationException ( Dictionary < string , string [] > errors )
: base ( "Validation failed" )
{
Errors = errors ;
}
}
// Middleware de manejo de errores
app . UseExceptionHandler ( errorApp =>
{
errorApp . Run ( async context =>
{
var exception = context . Features . Get < IExceptionHandlerFeature > () ?. Error ;
var ( statusCode , message ) = exception switch
{
NotFoundException => ( 404 , exception . Message ),
ValidationException ve => ( 400 , JsonSerializer . Serialize ( ve . Errors )),
UnauthorizedAccessException => ( 401 , "Unauthorized" ),
_ => ( 500 , "Internal server error" )
};
context . Response . StatusCode = statusCode ;
await context . Response . WriteAsJsonAsync ( new { error = message });
});
});
11. Flujos de Datos Principales
11.1 Flujo de DevSession + Streaming
sequenceDiagram
participant UI as Blazor UI
participant API as API Controller
participant DS as DevSessionService
participant HM as HistoryManager
participant AI as Claude API
participant DB as PostgreSQL
participant SR as SignalR
UI->>API: POST /api/dev-sessions/{id}/messages
API->>DS: SendMessageStreamAsync()
DS->>DB: Load session + messages
DS->>HM: GetOptimizedHistoryAsync()
HM->>DB: Get recent messages
HM-->>DS: Optimized history + summary
DS->>AI: Stream completion (SSE)
loop Streaming
AI-->>DS: Chunk
DS-->>API: Chunk
API-->>UI: SSE data
end
DS->>DB: Save user message
DS->>DB: Save assistant message
DS->>SR: Notify session subscribers
11.2 Flujo de Copilot Execution
stateDiagram-v2
[*] --> Idle: Create
Idle --> Planning: Start
Planning --> AwaitingApproval: Plan generated
Planning --> Failed: Error
AwaitingApproval --> Executing: Approve
AwaitingApproval --> Planning: Modify
AwaitingApproval --> Cancelled: Cancel
Executing --> Paused: Pause
Executing --> Completed: All steps done
Executing --> Failed: Max retries exceeded
Paused --> Executing: Resume
Paused --> Cancelled: Cancel
Completed --> [*]
Failed --> [*]
Cancelled --> [*]
11.3 Flujo de Batch Processing
sequenceDiagram
participant UI as UI
participant API as API
participant CBS as CopilotBatchService
participant CS as CopilotService
participant DB as DB
participant SR as SignalR
UI->>API: CreateBatch(backlogItemIds)
API->>CBS: CreateBatchAsync()
CBS->>DB: Create CopilotBatchExecution
CBS-->>API: batch
UI->>API: StartBatch(batchId)
API->>CBS: StartBatchAsync()
CBS->>DB: Get first item
CBS->>CS: StartAsync(objective)
loop For each item
CS->>CS: Execute steps
CS->>CBS: OnTaskCompletedAsync()
CBS->>DB: Update item status
CBS->>DB: Get next item
CBS->>SR: BatchProgressUpdated
alt Has next item
CBS->>CS: StartAsync(next)
else No more items
CBS->>SR: BatchCompleted
end
end
12. Guía de Integración
12.1 Agregar Nuevo Endpoint
// 1. Crear DTO
public record CreateFeatureRequest ( string Name , string Description );
public record FeatureDto ( Guid Id , string Name , string Description , DateTime CreatedAt );
// 2. Crear/extender servicio
public interface IFeatureService
{
Task < Feature > CreateAsync ( CreateFeatureRequest request );
Task < FeatureDto ?> GetAsync ( Guid id );
}
public class FeatureService : IFeatureService
{
private readonly NexusDbContext _db ;
public async Task < Feature > CreateAsync ( CreateFeatureRequest request )
{
var feature = new Feature
{
Name = request . Name ,
Description = request . Description
};
_db . Features . Add ( feature );
await _db . SaveChangesAsync ();
return feature ;
}
}
// 3. Crear controller
[ApiController]
[Route("api/[controller] ")]
public class FeaturesController : ControllerBase
{
private readonly IFeatureService _featureService ;
[HttpPost]
public async Task < ActionResult < FeatureDto >> Create ( CreateFeatureRequest request )
{
var feature = await _featureService . CreateAsync ( request );
return CreatedAtAction ( nameof ( Get ), new { id = feature . Id }, feature );
}
[HttpGet("{id}")]
public async Task < ActionResult < FeatureDto >> Get ( Guid id )
{
var feature = await _featureService . GetAsync ( id );
if ( feature == null ) return NotFound ();
return Ok ( feature );
}
}
// 4. Registrar en Program.cs
builder . Services . AddScoped < IFeatureService , FeatureService > ();
12.2 Agregar Notificación SignalR
// 1. Definir mensaje
public record FeatureCreatedMessage ( Guid Id , string Name , DateTime CreatedAt );
// 2. Inyectar IHubContext en el servicio
public class FeatureService : IFeatureService
{
private readonly IHubContext < AdminHub > _hubContext ;
public async Task < Feature > CreateAsync ( CreateFeatureRequest request )
{
var feature = // ... crear feature
// Notificar a suscriptores
await _hubContext . Clients . Group ( "dashboard" )
. SendAsync ( "FeatureCreated" , new FeatureCreatedMessage (
feature . Id ,
feature . Name ,
feature . CreatedAt
));
return feature ;
}
}
// 3. Suscribirse en Blazor
_hubConnection . On < FeatureCreatedMessage > ( "FeatureCreated" , message =>
{
_features . Insert ( 0 , message );
StateHasChanged ();
});
12.3 Agregar Integración Externa
// 1. Crear servicio de integración
public interface IExternalService
{
Task < ExternalData > FetchAsync ( string id );
Task PushAsync ( LocalData data );
}
public class ExternalService : IExternalService
{
private readonly HttpClient _httpClient ;
private readonly IConfiguration _config ;
public ExternalService ( HttpClient httpClient , IConfiguration config )
{
_httpClient = httpClient ;
_config = config ;
_httpClient . BaseAddress = new Uri ( config [ "External:BaseUrl" ] ! );
_httpClient . DefaultRequestHeaders . Authorization =
new AuthenticationHeaderValue ( "Bearer" , config [ "External:ApiKey" ]);
}
public async Task < ExternalData > FetchAsync ( string id )
{
var response = await _httpClient . GetAsync ( $"/api/data/{id}" );
response . EnsureSuccessStatusCode ();
return await response . Content . ReadFromJsonAsync < ExternalData > ();
}
}
// 2. Registrar con HttpClient factory
builder . Services . AddHttpClient < IExternalService , ExternalService > ();
// 3. Configurar en appsettings.json
{
"External" : {
"BaseUrl" : "https://api.external.com" ,
"ApiKey" : "..." // Usar secrets en producción
}
}
13. Testing
13.1 Estructura de Tests
Orchestrator.Tests/
├── Unit/
│ ├── Services/
│ │ ├── BacklogServiceTests.cs
│ │ ├── DevSessionServiceTests.cs
│ │ └── CopilotServiceTests.cs
│ └── Controllers/
│ └── BacklogControllerTests.cs
│
├── Integration/
│ ├── ApiTests/
│ │ └── BacklogApiTests.cs
│ └── DatabaseTests/
│ └── BacklogRepositoryTests.cs
│
└── Fixtures/
├── TestDbContext.cs
└── TestDataFactory.cs
13.2 Ejemplo de Test Unitario
public class BacklogServiceTests
{
private readonly Mock < NexusDbContext > _mockDb ;
private readonly Mock < IClickUpSyncService > _mockClickUp ;
private readonly BacklogService _service ;
public BacklogServiceTests ()
{
_mockDb = new Mock < NexusDbContext > ();
_mockClickUp = new Mock < IClickUpSyncService > ();
_service = new BacklogService ( _mockDb . Object , _mockClickUp . Object );
}
[Fact]
public async Task CreateAsync_ValidRequest_CreatesBacklogItem ()
{
// Arrange
var request = new CreateBacklogItemRequest (
Title : "Test item" ,
Description : "Test description" ,
ItemType : "bug" ,
Priority : "high" ,
ProjectId : Guid . NewGuid ()
);
var mockSet = new Mock < DbSet < BacklogItem >> ();
_mockDb . Setup ( d => d . BacklogItems ). Returns ( mockSet . Object );
// Act
var result = await _service . CreateAsync ( request , Guid . NewGuid ());
// Assert
Assert . NotNull ( result );
Assert . Equal ( request . Title , result . Title );
Assert . Equal ( "new" , result . Status );
mockSet . Verify ( s => s . Add ( It . IsAny < BacklogItem > ()), Times . Once );
}
[Fact]
public async Task ConvertToTaskAsync_ValidItem_CreatesScheduledTask ()
{
// Arrange
var backlogItem = new BacklogItem
{
Id = Guid . NewGuid (),
Title = "Test item" ,
ProjectId = Guid . NewGuid ()
};
// ... setup mocks
// Act
var task = await _service . ConvertToTaskAsync ( new ConvertToTaskRequest (
backlogItem . Id ,
AgentId : "code-assistant" ,
ProcedureId : null ,
ExecuteImmediately : false
));
// Assert
Assert . NotNull ( task );
Assert . Equal ( backlogItem . Id , task . SourceBacklogItemId );
}
}
13.3 Ejemplo de Test de Integración
public class BacklogApiTests : IClassFixture < WebApplicationFactory < Program >>
{
private readonly HttpClient _client ;
private readonly WebApplicationFactory < Program > _factory ;
public BacklogApiTests ( WebApplicationFactory < Program > factory )
{
_factory = factory ;
_client = factory . CreateClient ();
}
[Fact]
public async Task GetBacklog_ReturnsOk ()
{
// Act
var response = await _client . GetAsync ( "/api/backlog?projectId=..." );
// Assert
response . EnsureSuccessStatusCode ();
var content = await response . Content . ReadFromJsonAsync < BacklogListResponse > ();
Assert . NotNull ( content );
}
[Fact]
public async Task CreateBacklogItem_ValidRequest_ReturnsCreated ()
{
// Arrange
var request = new CreateBacklogItemRequest (
Title : "Integration test item" ,
Description : "Test" ,
ItemType : "feature" ,
Priority : "medium" ,
ProjectId : Guid . Parse ( "..." )
);
// Act
var response = await _client . PostAsJsonAsync ( "/api/backlog" , request );
// Assert
Assert . Equal ( HttpStatusCode . Created , response . StatusCode );
}
}
14. Troubleshooting
14.1 Problemas Comunes
Problema
Causa
Solución
DbContext disposed
Uso de DbContext después de que el scope terminó
Usar IServiceScopeFactory para crear nuevo scope
Deadlock en SignalR
Llamar Clients.*.SendAsync dentro de lock
Mover la notificación fuera del lock
Token timeout
Respuesta muy larga de Claude
Aumentar TimeoutMinutes en CopilotExecution
Memory leak
No disponer HubConnection
Implementar IAsyncDisposable
Concurrent update
Múltiples updates al mismo registro
Usar SemaphoreSlim por entidad
14.2 Logs y Debugging
// Habilitar logs detallados en appsettings.Development.json
{
"Serilog" : {
"MinimumLevel" : {
"Default" : "Debug" ,
"Override" : {
"Microsoft.EntityFrameworkCore" : "Warning" ,
"Microsoft.AspNetCore" : "Information"
}
}
}
}
// Agregar contexto a logs
Log . ForContext ( "SessionId" , sessionId )
. ForContext ( "UserId" , userId )
. Information ( "Processing message for session" );
// Query logs en PostgreSQL
SELECT * FROM "SystemLogs"
WHERE "SessionId" = ' guid '
ORDER BY "Timestamp" DESC
LIMIT 100 ;
14.3 Health Checks
// Endpoint de health check
GET / health
// Respuesta
{
"status" : "Healthy" ,
"checks" : {
"database" : "Healthy" ,
"signalr" : "Healthy" ,
"claude_api" : "Healthy"
}
}
Apéndice A: Referencias Rápidas
Variables de Entorno
Variable
Descripción
Ejemplo
ConnectionStrings__NexusDb
PostgreSQL connection string
Host=localhost;Database=nexus;...
Anthropic__ApiKey
API key de Claude
sk-ant-...
ClickUp__ApiToken
Token de ClickUp
pk_...
Sentry__Dsn
DSN de Sentry
https://...@sentry.io/...
Puertos por Defecto
Servicio
Puerto
URL
Orchestrator.Api
5000
http://localhost:5000
Orchestrator.Admin
5001
http://localhost:5001
PostgreSQL
5432
-
RabbitMQ
5672/15672
http://localhost:15672
Comandos Útiles
# Iniciar API
cd Orchestrator/src/Orchestrator.Api && dotnet run
# Iniciar Admin UI
cd Orchestrator/src/Orchestrator.Admin && dotnet run
# Ejecutar tests
dotnet test Orchestrator.Tests/
# Aplicar migraciones
dotnet ef database update --project Shared/Shared.Admin
# Generar migración
dotnet ef migrations add MigrationName --project Shared/Shared.Admin
Historial de Cambios
Versión
Fecha
Cambios
1.0
2026-02-01
Versión inicial
Documento generado para el equipo de desarrollo de Calmia Nexus