Qué hago
Guío al orchestrator para crear funcionalidades nuevas (no modificar existentes) con análisis previo de reutilización, configuración y compatibilidad. A diferencia de los refactors, aquí no hay código previo que preservar: el diseño empieza desde los requisitos del usuario.
Cuándo usar este skill (en lugar de refactor-protocol):
- Usuario pide un endpoint, servicio, job, handler, evento o configuración que NO existe
- La tarea es creación pura, no modificación de comportamiento existente
- El resultado añade archivos al repositorio, no cambia la signatura de los existentes
Cuándo NO usar este skill (usar refactor-protocol en su lugar):
- Modificar signatura de un servicio existente
- Cambiar comportamiento de un endpoint que ya existe
- Renombrar, extraer o mover código
FASE 0 — Validación de estructura (siempre primera)
- Cargar el skill
project-structurey ejecutar la validación completa - Detectar el tipo de host:
FUNCTIONS|WEBAPP|NUGET_LIBRARY - Actuar según el resultado:
| Estado | Acción |
|---|---|
HARD_BLOCK | Detener. Informar al usuario. |
NUGET_LIBRARY | Crear un endpoint HTTP es inesperado. Preguntar: "¿Quieres añadir una operación pública a la librería o realmente hay un host que no he detectado?" |
PREGUNTA | Resolver las preguntas antes de continuar |
OK / WARNING | Continuar a FASE 1 |
FASE 1 — Recopilación de requisitos
El orchestrator infiere lo que pueda de la descripción del usuario ($ARGUMENTS) y solo pregunta
lo que no esté claro. No hacer preguntas cuya respuesta ya esté en la descripción.
1.1 Nombre, propósito y tipo de feature
- Endpoint HTTP (Function/Controller):
[VERBO] /ruta — descripción - Servicio de dominio: nombre del servicio y responsabilidad
- Job/Event handler: trigger + acción
- Opción de configuración: nombre de la opción y su propósito
1.2 Clasificación READ vs MUTATION
Determinar si la feature muta estado o es solo lectura. Esto afecta a:
- Dónde van los DTOs de response
- Si se requiere transacción / idempotencia
- Qué tests se necesitan
| Tipo | Características | Implicación |
|---|---|---|
| READ-ONLY | GET, devuelve datos existentes, no escribe | DTOs efímeros, tests mínimos |
| MUTATION | POST/PUT/DELETE, escribe o cambia estado | Requiere tests de éxito + error |
1.3 Recopilación de input/output esperado
- Request: estructura del body o query params
- Response: estructura del body de respuesta, status codes
- Errores: qué errores se deben mapear a qué status codes
FASE 1.5 — Análisis de reutilización (CRÍTICA, no saltarse)
Objetivo: detectar si la feature ya existe parcial o totalmente antes de crear nada.
Pasos
- Buscar servicios existentes en
*.Impl/con@exploreusando el dominio de la feature - Buscar métodos candidatos que cubran el 80%+ del comportamiento requerido:
- ¿Hay un método que ya devuelve el dato necesario con un filtro trivial?
- ¿Hay un método que se podría sobrecargar para añadir el caso?
- Buscar endpoints similares en
*.API/con la misma forma de request/response
Árbol de decisión
¿Existe método que cubre 100% del caso?
└── SÍ → NO crear método nuevo. Crear solo:
· Endpoint (Function/Controller) en *.API/
· DTO Request/Response exclusivo en *.API/
· Test del shape del response
¿Existe método casi-idóneo (filtro, paginación, orden)?
├── SÍ → considerar:
│ ├── Añadir parámetro opcional al método existente (con valor por defecto)
│ ├── Crear overload con signatura más específica
│ └── Crear método de extensión que envuelva al existente
└── NO → continuar con creación de método nuevo
¿Existe servicio relacionado pero diferente (e.g., similar responsabilidad)?
├── SÍ → preguntar al usuario: "¿Extiendo [CSXxxService] o creo uno nuevo [CSYyyService]?"
└── NO → crear servicio nuevo
Salida esperada de esta fase
Una de estas tres decisiones, presentada al usuario antes de continuar:
- Reutilización pura: la feature se implementa como wrapper de un método existente
- Extensión: añadir overload/método a un servicio existente
- Creación nueva: servicio y métodos nuevos (justificar por qué no se puede reutilizar)
FASE 1.6 — Análisis de configuración (NUEVA)
Objetivo: detectar si la feature requiere parámetros configurables y, si sí, dónde definirlos.
Preguntas clave
- ¿La feature tiene valores por defecto razonables sin configuración externa?
- ¿Hay umbrales, paginación, timeouts, o flags que deberían ser configurables por entorno?
- ¿Se conecta a un sistema externo con URL/clave/secret?
- ¿Necesita feature flags para rollout gradual?
Árbol de decisión
¿La feature requiere configuración?
├── NO → no crear IXxxOptions, no tocar AddCustomConfiguration
└── SÍ → continuar
¿Qué tipo de configuración?
├── Connection string o secreto
│ └── Usar IConfiguration con nombre en appsettings.json (gestionado por AddCustomConfiguration)
├── Valor simple (umbral, timeout, flag)
│ └── Crear IXxxOptions en *.Impl/ (interface) + añadir a AddCustomConfiguration<T>("key")
└── Lista de valores (allowed origins, mappings)
└── Crear IXxxOptions con propiedad de tipo IEnumerable<T>
Convención del proyecto
El proyecto usa AddCustomConfiguration<IXxxOptions>("config-section") en IoCExtensions.cs
(líneas 43-45 del proyecto actual). Las interfaces viven en *.Impl/ y se referencian desde
*.Impl/Services/.
Salida esperada
Si la feature requiere configuración, presentar al usuario:
- Nombre de la interface (e.g.,
IPendingPaymentOptions) - Propiedades con sus tipos y valores por defecto
- Sección en
appsettings.json(e.g.,"RAMBLA.PendingPayment") - Modificación de
IoCExtensions.AddCSLevelBooking()
Si NO requiere, indicarlo explícitamente en el plan.
FASE 1.7 — Análisis de impacto en API pública
Objetivo: evitar romper consumers existentes.
Si la feature reutiliza un método existente
- ¿El método se mantiene con la misma signatura? → sin impacto
- ¿Se añade un parámetro opcional? → verificar que ningún call site se rompa
- ¿Se añade un overload? → verificar que el compilador resuelva sin ambigüedad
- ¿Se renombra? → buscar todos los call sites con
grepy migrar
Si la feature añade un servicio nuevo
- ¿El servicio se registra en DI? → asegurar registro en
IoCExtensions - ¿Algún servicio existente tiene dependencia circular potencial? → verificar
Si la feature añade un endpoint nuevo
- ¿La ruta colisiona con otra ruta existente? → verificar
- ¿El método HTTP es coherente con REST? (GET para lectura, POST para mutación)
FASE 1.8 — PREFLIGHT de tests de regresión (obligatorio)
Objetivo: antes de generar código nuevo o modificar existente, verificar que los componentes que se van a tocar (aunque sea tangencialmente) tienen cobertura de tests. Si no la tienen, crearla y ejecutarla antes de iniciar la implementación.
Pasos
-
Identificar superficie tangencial: a partir de FASE 1.5 (reutilización vs extensión vs nuevo), listar:
- Servicios existentes cuyos métodos se van a extender o sobrecargar
- Interfaces existentes que se van a modificar
- Endpoints existentes que pueden verse afectados por routing/DI
- Cualquier archivo existente que se va a editar (no crear)
-
Para cada componente existente que se va a tocar:
- Buscar tests en
src/*.Test*/**que lo ejerciten - Si tiene cobertura → OK, continuar
- Si no tiene cobertura → HARD BLOCK:
- Invocar
@refactor-worker-testspara crear tests de caracterización del comportamiento actual - Invocar
@refactor-verifierpara confirmar que pasan en verde - Commit separado:
test(characterization): add characterization tests for <clases>
- Invocar
- Buscar tests en
-
Si la feature es 100% creación nueva (no toca archivos existentes):
- No hay superficie de regresión → omitir PREFLIGHT
- Pero los tests de la feature nueva se crean en FASE 4 y deben pasar en FASE 5
Criterio de cierre (FASE 5)
Para dar por correcta la feature, @refactor-verifier debe confirmar que pasan:
- Tests nuevos de la feature (servicio + endpoint)
- Tests existentes de los componentes tangenciales (regresión)
Si cualquiera falla, la feature no puede cerrarse.
FASE 2 — Árbol de decisión: qué crear y dónde
Aplicar la decisión de la FASE 1.5 (reutilización / extensión / nuevo) más la FASE 1.6 (config).
Para endpoint READ-ONLY con reutilización
*.API/
+ {Nombre}Function.cs — Function/Controller nuevo
+ {Nombre}Request.cs — exclusivo de presentación
+ {Nombre}Response.cs — exclusivo de presentación
*.Test*/
+ {Nombre}FunctionTests.cs — test de shape del response (NO lógica)
Para endpoint MUTATION con servicio nuevo
*.Contracts/
+ ICS{Nombre}Service.cs — interfaz del servicio
+ {Nombre}Request.cs — si Request compartido
+ {Nombre}Response.cs — si Response compartido
*.Impl/
+ CS{Nombre}Service.cs — implementación
+ I{Nombre}Options.cs — SOLO si FASE 1.6 detectó config
*.API/
+ {Nombre}Function.cs — Function/Controller
+ {Nombre}Request.cs — exclusivo (si no se puso en Contracts)
+ {Nombre}Response.cs — exclusivo (si no se puso en Contracts)
*.Test*/
+ CS{Nombre}ServiceTests.cs — happy path + al menos un error
Para extensión de servicio existente
*.Contracts/
~ ICS{Nombre}Service.cs — añadir firma del método (o crear overload)
*.Impl/
~ CS{Nombre}Service.cs — implementar el nuevo método/overload
*.API/
+ {Nombre}Function.cs — Function/Controller nuevo
+ {Nombre}Request.cs — si exclusivo
+ {Nombre}Response.cs — si exclusivo
*.Test*/
+ (o ~) tests del servicio — añadir caso para el nuevo método
FASE 3 — Plan de creación
Antes de invocar workers, el orchestrator presenta este plan al usuario y espera confirmación explícita:
[PLAN DE CREACIÓN] Feature: <nombre> — <descripción breve>
Host: <FUNCTIONS | WEBAPP>
Clasificación: <READ-ONLY | MUTATION>
Estrategia: <REUTILIZACIÓN | EXTENSIÓN | NUEVO>
Análisis de reutilización (FASE 1.5):
· Servicio candidato: <ICSXxxService> o "ninguno"
· Método candidato: <XxxService.YyyMethod> o "ninguno"
· Decisión: <REUTILIZACIÓN | EXTENSIÓN | NUEVO>
Análisis de configuración (FASE 1.6):
· Requiere config: <SÍ | NO>
· Si sí: <IXxxOptions + sección appsettings>
Impacto en API pública (FASE 1.7):
· Call sites afectados: <lista> o "ninguno"
· Breaking change: <SÍ | NO>
Artefactos a crear:
*.Contracts/ ([N] archivos):
+ ICS{Nombre}Service.cs — nueva interfaz (o: añadir método a IXxx)
+ {Nombre}Request.cs — si tipo compartido
+ {Nombre}Response.cs — si tipo compartido
*.Impl/ ([N] archivos):
+ CS{Nombre}Service.cs — implementación del servicio
+ I{Nombre}Options.cs — SOLO si requiere config (FASE 1.6)
*.API/ ([N] archivos):
+ {Nombre}Function.cs — Azure Function (o: {Nombre}Controller.cs)
+ {Nombre}Request.cs — si tipo exclusivo
+ {Nombre}Response.cs — si tipo exclusivo
*.Test*/ ([N] archivos):
+ CS{Nombre}ServiceTests.cs — tests del servicio (happy path + error)
+ {Nombre}FunctionTests.cs — tests del endpoint (si aplica)
Modificaciones a archivos existentes:
~ src/.../IoCExtensions.cs — registrar servicio y opciones (si aplica)
Orden de ejecución: Contracts → Impl → Presentation → Tests
¿Proceder?
Sin aprobación explícita del usuario, no invocar workers.
FASE 4 — Ejecución
Orden de workers
@refactor-worker-contracts— solo si hay nuevos tipos o signaturas en*.Contracts/@refactor-worker-impl— siempre que haya código en*.Impl/@refactor-worker-presentation— siempre que haya un endpoint@refactor-worker-tests— siempre al final
Nota: NO se invoca
@refactor-worker-crosscuttingsalvo que la feature exponga un nuevo Service Gateway. Esto es un caso raro en creación; suele ser en refactors.
Instrucciones a cada worker
Incluir en el prompt:
- El plan completo (FASE 3)
- Los patrones del proyecto: leer un archivo similar antes de crear
- La lista exacta de archivos con su contenido esperado
- Si FASE 1.6 detectó config: el path de
IXxxOptionsy la sección de configuración
FASE 5 — Verificación y commit
- Invocar
@refactor-verifier - Si PASS → proponer commit con formato:
feat(<scope>): add <nombre-corto> feature - Added ICS{Nombre}Service interface and CS{Nombre}Service implementation - Added {VERB} /{ruta} Function/Controller - Added {Nombre}Request/{Nombre}Response contracts - Added I{Nombre}Options for new configuration (si aplica) - Added unit tests for CS{Nombre}Service - Si FAIL → identificar capa fallida, re-invocar worker, re-verificar
Patrones por host
FUNCTIONS — Azure Functions Isolated Worker
Antes de crear la Function, leer una Function HTTP existente y copiar su estilo. Ejemplo típico:
public class {Nombre}Function
{
private readonly I{Nombre}Service _{campo};
public {Nombre}Function(I{Nombre}Service {campo}) => _{campo} = {campo};
[Function("{Nombre}Function")]
public async Task<HttpResponseData> RunAsync(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "{ruta}")] HttpRequestData req)
{
var request = await req.ReadFromJsonAsync<{Nombre}Request>();
var result = await _{campo}.ExecuteAsync(request);
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(result);
return response;
}
}
Verificar antes de crear:
- ¿
AuthorizationLevel.FunctionoAnonymous? (copiar del proyecto) - ¿
ReadFromJsonAsynco lectura manual? (copiar del proyecto) - ¿DI registrado en
IoCExtensions.AddCSLevelBooking()? (añadir si no)
WEBAPP — ASP.NET Core
Antes de crear el Controller/Endpoint, leer uno existente:
- ¿Controllers con
[ApiController]o Minimal API? - ¿
IActionResult/ActionResult<T>oResults<T>? - ¿Validación con FluentValidation, DataAnnotations u otro?
Copiar el estilo existente.
Non-HTTP triggers (Timer, Service Bus, Event Grid)
Antes de crear el trigger, leer uno existente en el proyecto y copiar su estilo. Si no hay ninguno, usar estos patrones de referencia.
Timer trigger
public class {Nombre}Function
{
private readonly I{Nombre}Service _{campo};
public {Nombre}Function(I{Nombre}Service {campo}) => _{campo} = {campo};
[Function("{Nombre}Function")]
public async Task RunAsync(
[TimerTrigger("0 */5 * * * *")] TimerInfo timer,
CancellationToken ct)
{
await _{campo}.ExecuteAsync(ct);
}
}
- El CRON expression
0 */5 * * * *significa cada 5 minutos (NCRONTAB, 6 campos). TimerInfopuede ser null en local; no asumir que tiene valor.- Registrar el servicio en
IoCExtensionscomo siempre.
Service Bus trigger
public class {Nombre}Function
{
private readonly I{Nombre}Service _{campo};
public {Nombre}Function(I{Nombre}Service {campo}) => _{campo} = {campo};
[Function("{Nombre}Function")]
public async Task RunAsync(
[ServiceBusTrigger("%{Nombre}QueueName%", Connection = "ServiceBusConnection")]
string message,
CancellationToken ct)
{
var request = JsonSerializer.Deserialize<{Nombre}Request>(message);
await _{campo}.ExecuteAsync(request!, ct);
}
}
%{Nombre}QueueName%resuelve desdeappsettings.json(clave{Nombre}QueueNamecon valor del nombre de la cola/Topic).Connection = "ServiceBusConnection"resuelve desdeappsettings.jsono Key Vault (string de conexión, no entity ID).- Requiere paquete
APS.Messaging.ServiceBus(veraps-packages).
Event Grid trigger
public class {Nombre}Function
{
private readonly I{Nombre}Service _{campo};
public {Nombre}Function(I{Nombre}Service {campo}) => _{campo} = {campo};
[Function("{Nombre}Function")]
public async Task RunAsync(
[EventGridTrigger] EventGridEvent eventGridEvent,
CancellationToken ct)
{
var data = eventGridEvent.Data.ToObjectFromJson<{Nombre}EventData>();
await _{campo}.ExecuteAsync(data!, ct);
}
}
EventGridEventviene del namespaceMicrosoft.Azure.Functions.Worker.- Requiere paquete
APS.Messaging.EventGrid(veraps-packages).
Ubicación de artefactos para non-HTTP triggers
Igual que endpoint HTTP, pero sin Request/Response de presentación (el input viene del binding, no del body HTTP). El árbol de decisión de FASE 2 se aplica igual para Contracts/Impl/Tests.
*.Contracts/
+ ICS{Nombre}Service.cs — interfaz del servicio
+ {Nombre}Request.cs — modelo del input (deserializado del mensaje)
*.Impl/
+ CS{Nombre}Service.cs — implementación
*.API/ (o *.Functions/)
+ {Nombre}Function.cs — Function con trigger (sin HttpResponseData)
*.Test*/
+ CS{Nombre}ServiceTests.cs — tests del servicio (happy path + error)
Si el trigger es un Timer (no hay input externo),
{Nombre}Requestno es necesario. El servicio recibeCancellationTokenúnicamente.
Anti-patrones
- ❌ Crear un servicio nuevo sin haber pasado por FASE 1.5 (análisis de reutilización)
- ❌ Añadir parámetros de configuración hardcodeados en lugar de
IXxxOptions - ❌ Usar
IConfigurationdirectamente en el servicio (debe inyectarseIXxxOptions) - ❌ Crear DTOs en
*.Contracts/cuando son exclusivos del endpoint - ❌ Hacer commit sin haber pasado por FASE 5 (verificación)
- ❌ Saltarse la aprobación del plan (FASE 3) por ahorrar tiempo
- ❌ Olvidar registrar el servicio en
IoCExtensions.AddCSLevelBooking()
Cobertura de creación de endpoints
Anteriormente existía un skill dedicado add-endpoint que fue eliminado por redundancia:
crear un endpoint HTTP no es un refactor (no hay código previo que preservar) sino creación
pura, que es exactamente lo que cubre add-feature. Si el usuario describe un endpoint,
el orchestrator detecta que es un caso READ-ONLY o MUTATION de creación de feature y aplica
el árbol de decisión simplificado de este skill.
Invocar el command /add-feature y describir el endpoint (verbo HTTP, ruta, propósito) es
suficiente.