claude-code-arcane 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +2 -0
  3. package/agents/engineering/dotnet-engineer.md +78 -0
  4. package/docs/SKILLS-CATALOG.md +26 -3
  5. package/package.json +1 -1
  6. package/profiles/backend-dotnet.yaml +54 -0
  7. package/profiles/job-hunt.yaml +35 -0
  8. package/profiles/unity-design.yaml +1 -0
  9. package/profiles/unity-dev.yaml +1 -0
  10. package/rules/dotnet-code.md +64 -0
  11. package/skills/cold-outreach/SKILL.md +65 -0
  12. package/skills/cold-outreach/references/recruiter-playbook.md +65 -0
  13. package/skills/cover-letter/SKILL.md +66 -0
  14. package/skills/cv-ats-export/SKILL.md +64 -0
  15. package/skills/cv-ats-export/scripts/cv_export.py +306 -0
  16. package/skills/cv-tailor/SKILL.md +70 -0
  17. package/skills/cv-tailor/references/ats-keywords.md +46 -0
  18. package/skills/dotnet-architecture/SKILL.md +66 -0
  19. package/skills/dotnet-architecture/references/anti-patterns.md +12 -0
  20. package/skills/dotnet-architecture/references/checklist.md +19 -0
  21. package/skills/dotnet-architecture/references/patterns.md +118 -0
  22. package/skills/dotnet-architecture/references/project-structure.md +78 -0
  23. package/skills/dotnet-best-practices/SKILL.md +76 -0
  24. package/skills/dotnet-best-practices/references/api-design.md +75 -0
  25. package/skills/dotnet-best-practices/references/architecture.md +62 -0
  26. package/skills/dotnet-best-practices/references/async.md +62 -0
  27. package/skills/dotnet-best-practices/references/database.md +69 -0
  28. package/skills/dotnet-best-practices/references/dependency-injection.md +73 -0
  29. package/skills/dotnet-best-practices/references/devops.md +76 -0
  30. package/skills/dotnet-best-practices/references/error-handling.md +72 -0
  31. package/skills/dotnet-best-practices/references/performance.md +63 -0
  32. package/skills/dotnet-best-practices/references/security.md +73 -0
  33. package/skills/dotnet-best-practices/references/testing.md +76 -0
  34. package/skills/dotnet-scaffold/SKILL.md +99 -0
  35. package/skills/install-mcp/SKILL.md +107 -0
  36. package/skills/install-mcp/references/manual-setup.md +92 -0
  37. package/skills/interview-prep/SKILL.md +69 -0
  38. package/skills/interview-prep/references/star-framework.md +42 -0
  39. package/skills/job-hunt/SKILL.md +92 -0
  40. package/skills/job-hunt/references/templates/Aplicacion.md +48 -0
  41. package/skills/job-hunt/references/templates/CV Custom.md +53 -0
  42. package/skills/job-hunt/references/templates/Contacto.md +30 -0
  43. package/skills/job-hunt/references/templates/Dashboard.md +45 -0
  44. package/skills/job-hunt/references/templates/Empresa.md +36 -0
  45. package/skills/job-hunt/references/templates/Entrevista.md +44 -0
  46. package/skills/job-hunt/references/templates/Perfil.md +38 -0
  47. package/skills/job-search/SKILL.md +83 -0
  48. package/skills/job-search/references/scoring-rubric.md +43 -0
  49. package/skills/linkedin-optimize/SKILL.md +79 -0
  50. package/skills/master-profile/SKILL.md +69 -0
  51. package/skills/network-map/SKILL.md +61 -0
  52. package/skills/network-map/scripts/network_map.py +109 -0
  53. package/skills/personal-brand/SKILL.md +54 -0
  54. package/skills/personal-brand/references/post-pillars.md +66 -0
  55. package/skills/portfolio-site/SKILL.md +59 -0
@@ -0,0 +1,78 @@
1
+ # Estructura de Directorios
2
+
3
+ Dos formas válidas de estructurar un backend .NET 10 / ASP.NET Core. Elegí según
4
+ complejidad del dominio (ver `when-to-use` en la SKILL).
5
+
6
+ ## Vertical Slice Architecture (un proyecto)
7
+
8
+ Cada feature es un folder autocontenido: request, handler, validator y endpoint
9
+ viven juntos. Tocás un caso de uso → tocás un solo archivo/carpeta.
10
+
11
+ ```
12
+ src/
13
+ Features/
14
+ Orders/
15
+ CreateOrder.cs ← record Command + Handler + Validator juntos
16
+ GetOrders.cs ← record Query + Handler (paginación/proyección)
17
+ OrderEndpoints.cs ← MapGroup("/orders") + TypedResults
18
+ Customers/
19
+ RegisterCustomer.cs
20
+ CustomerEndpoints.cs
21
+ Common/
22
+ Behaviors/ ← ValidationBehavior, LoggingBehavior, TxBehavior
23
+ Result.cs ← Result<T> / Error compartido
24
+ Infrastructure/
25
+ AppDbContext.cs ← EF Core DbContext (DbSet<> por entidad)
26
+ Migrations/ ← migraciones versionadas en git
27
+ Program.cs ← composition root: DI, pipeline, MapEndpoints
28
+ appsettings.json
29
+ ```
30
+
31
+ - Cada slice depende de `Common` e `Infrastructure`, no de otros slices.
32
+ - Un slice nuevo = un archivo nuevo; borrarlo no rompe a los demás (bajo acoplamiento).
33
+ - El `DbContext` es compartido; cada handler usa solo los `DbSet<>` que necesita.
34
+
35
+ ## Clean Architecture (cuatro proyectos)
36
+
37
+ Separación por capa técnica. Las dependencias apuntan **hacia adentro**: el dominio
38
+ no conoce a nadie; la infraestructura y la API dependen de las capas internas.
39
+
40
+ ```
41
+ src/
42
+ MyApp.Domain/ ← Entidades, value objects, domain events, reglas
43
+ Entities/Order.cs ← cero deps externas (ni EF, ni ASP.NET)
44
+ ValueObjects/Money.cs
45
+ Common/Entity.cs
46
+ MyApp.Application/ ← Casos de uso (handlers), DTOs, interfaces
47
+ Orders/CreateOrder.cs
48
+ Common/IApplicationDbContext.cs ← expone DbSet<> + SaveChangesAsync
49
+ Common/Behaviors/
50
+ MyApp.Infrastructure/ ← EF Core, Identity, servicios externos
51
+ Persistence/AppDbContext.cs ← : DbContext, IApplicationDbContext
52
+ Persistence/Migrations/
53
+ Services/EmailSender.cs
54
+ DependencyInjection.cs
55
+ MyApp.Api/ ← Endpoints, Program.cs, wiring de DI
56
+ Endpoints/OrderEndpoints.cs
57
+ Program.cs
58
+ ```
59
+
60
+ ### Dirección de las referencias (`.csproj`)
61
+
62
+ ```
63
+ MyApp.Api → referencia → MyApp.Infrastructure, MyApp.Application
64
+ MyApp.Infrastructure → referencia → MyApp.Application
65
+ MyApp.Application → referencia → MyApp.Domain
66
+ MyApp.Domain → referencia → (NADA)
67
+ ```
68
+
69
+ - `Domain.csproj` no tiene ningún `<ProjectReference>` ni paquete de framework.
70
+ - `Application` define `IApplicationDbContext`; `Infrastructure` lo implementa.
71
+ Así la lógica de negocio depende de una abstracción, no de EF Core directamente.
72
+ - La inversión de dependencias se logra en `Api/Program.cs`, que es el único lugar
73
+ que conoce a las cuatro capas y arma el grafo de DI.
74
+
75
+ > Regla de oro: si una flecha de dependencia apunta hacia afuera del dominio, la
76
+ > arquitectura está rota. El dominio es el centro estable.
77
+
78
+ _Ref: https://github.com/ardalis/CleanArchitecture · https://github.com/nadirbad/VerticalSliceArchitecture · https://www.milanjovanovic.tech/blog/vertical-slice-architecture-dotnet_
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: dotnet-best-practices
3
+ description: "ASP.NET Core / C# production best practices: 40 reglas en 10 categorías (arquitectura, DI, errores, seguridad, performance, async, EF Core, testing, API, devops) priorizadas por impacto. Usar al escribir/revisar código .NET backend."
4
+ category: "backend"
5
+ argument-hint: "[architecture|di|errors|security|performance|async|database|testing|api|all]"
6
+ user-invocable: true
7
+ allowed-tools: Read, Glob, Grep, Bash, Write, Edit, Task
8
+ ---
9
+ # dotnet-best-practices — Guía priorizada por impacto
10
+
11
+ 40 reglas para aplicaciones ASP.NET Core (.NET 10) production-ready, organizadas en 10 categorías y priorizadas de **CRITICAL** a **LOW-MEDIUM**. Cada regla del catálogo (en `references/`) trae impacto, ejemplo incorrecto, ejemplo correcto y link a docs.
12
+
13
+ ## MANDATORY WORKFLOW
14
+
15
+ **No aplicar las 40 reglas a ciegas. Diagnosticar primero, priorizar por impacto.**
16
+
17
+ ### Step 0: Determinar scope
18
+
19
+ 1. **¿Código nuevo o auditoría de existente?** Si es auditoría, leer primero el feature/proyecto objetivo.
20
+ 2. **¿Qué categoría aplica?** Mapear el área del problema (ver tabla) o usar `all` para review completa.
21
+ 3. **¿Hay síntomas concretos?** (deadlocks, N+1, 500 inconsistentes, leaks) → ir directo a la categoría relevante.
22
+
23
+ ### Step 1: Priorizar por impacto
24
+
25
+ Aplicar en este orden — no bajar de nivel hasta resolver el anterior:
26
+
27
+ | Prioridad | Categoría | Impacto | Reference |
28
+ |-----------|-----------|---------|-----------|
29
+ | 1 | Architecture | CRITICAL | `references/architecture.md` |
30
+ | 2 | Dependency Injection | CRITICAL | `references/dependency-injection.md` |
31
+ | 3 | Async & Concurrency | HIGH | `references/async.md` |
32
+ | 4 | Error Handling | HIGH | `references/error-handling.md` |
33
+ | 5 | Security | HIGH | `references/security.md` |
34
+ | 6 | Performance | HIGH | `references/performance.md` |
35
+ | 7 | Database & EF Core | MEDIUM-HIGH | `references/database.md` |
36
+ | 8 | Testing | MEDIUM-HIGH | `references/testing.md` |
37
+ | 9 | API Design | MEDIUM | `references/api-design.md` |
38
+ | 10 | DevOps & Deployment | LOW-MEDIUM | `references/devops.md` |
39
+
40
+ ### Step 2: Aplicar + verificar
41
+
42
+ Para cada regla relevante: leer el ejemplo correcto en el reference, aplicarlo, y verificar contra el checklist de cierre.
43
+
44
+ ### Step 3: Checklist de cierre
45
+
46
+ - [ ] `<Nullable>enable</Nullable>` + warnings as errors; cero `#nullable disable`
47
+ - [ ] Async end-to-end con `CancellationToken`; cero `.Result`/`.Wait()`/`.GetAwaiter().GetResult()`
48
+ - [ ] DI por constructor con scope correcto; sin `BuildServiceProvider()` manual ni service locator
49
+ - [ ] Errores vía `ProblemDetails` (RFC 7807); sin stack traces expuestos
50
+ - [ ] Sin N+1 en EF Core (`Include`/projection); `AsNoTracking()` en lecturas
51
+ - [ ] Migraciones versionadas; nunca `EnsureCreated()` en prod
52
+ - [ ] AuthZ por policies declarativas; secrets en secret manager (no en `appsettings.json`)
53
+ - [ ] Structured logging (Serilog JSON) + health checks
54
+ - [ ] Tests con xUnit; integración contra Postgres real (Testcontainers), no mocks de DbContext
55
+
56
+ Si todo el checklist pasa → código **COMPLIANT**. Antes de escribir cambios significativos, confirmar el approach con el usuario (Question → Decision → Approval).
57
+
58
+ ## Tabla de mapeo síntoma → categoría
59
+
60
+ | Síntoma | Categoría |
61
+ |---------|-----------|
62
+ | Deadlocks / thread pool starvation / app cuelga | Async, Performance |
63
+ | Lentitud en endpoints, muchas queries | Performance, Database |
64
+ | `InvalidOperationException` de DI / scopes / captured dependency | DI, Architecture |
65
+ | Errores 500 inconsistentes / leaks de stack traces | Error Handling |
66
+ | Endpoints expuestos / brute force / data sensible en logs | Security |
67
+ | Tests frágiles o que pegan a servicios/DB reales sin control | Testing |
68
+ | Respuestas inconsistentes / over-fetching de entidades | API Design |
69
+
70
+ ## Próximos pasos
71
+
72
+ Para estructurar el proyecto → `/dotnet-architecture`. Para iniciar un backend nuevo con estos defaults → `/dotnet-scaffold`.
73
+
74
+ ---
75
+
76
+ _Adaptado de [github/awesome-copilot](https://github.com/github/awesome-copilot) (dotnet-best-practices), [dotnet/skills](https://github.com/dotnet/skills) y [Aaronontheweb/dotnet-skills](https://github.com/Aaronontheweb/dotnet-skills). Reorganizado al formato skill+references de Arcane._
@@ -0,0 +1,75 @@
1
+ # 9. API Design (MEDIUM)
2
+
3
+ ## 9.1 DTOs, Never Expose EF Entities — MEDIUM
4
+
5
+ Serializar entidades de EF filtra columnas internas, arrastra relaciones y acopla el contrato HTTP al schema de la base. Usar DTOs/records dedicados.
6
+
7
+ **Incorrecto:**
8
+ ```csharp
9
+ app.MapGet("/users/{id}", async (int id, AppDbContext db) => await db.Users.FindAsync(id)); // expone la entidad
10
+ ```
11
+ **Correcto:**
12
+ ```csharp
13
+ public record UserDto(int Id, string Email);
14
+ app.MapGet("/users/{id}", async (int id, AppDbContext db) =>
15
+ await db.Users.Where(u => u.Id == id).Select(u => new UserDto(u.Id, u.Email)).FirstOrDefaultAsync());
16
+ ```
17
+
18
+ ## 9.2 API Versioning — MEDIUM
19
+
20
+ Versionar la API permite evolucionar el contrato sin romper clientes existentes. `Asp.Versioning` lo integra con minimal APIs y OpenAPI.
21
+
22
+ **Correcto:**
23
+ ```csharp
24
+ builder.Services.AddApiVersioning(o => o.DefaultApiVersion = new ApiVersion(1, 0));
25
+ var v1 = app.NewVersionedApi().MapGroup("/api/v{version:apiVersion}").HasApiVersion(1.0);
26
+ v1.MapGet("/orders", GetOrders);
27
+ ```
28
+
29
+ ## 9.3 Consistent Errors with ProblemDetails — MEDIUM
30
+
31
+ `ProblemDetails` (RFC 9457) da un formato de error estándar y machine-readable en toda la API.
32
+
33
+ **Correcto:**
34
+ ```csharp
35
+ builder.Services.AddProblemDetails();
36
+ app.UseExceptionHandler();
37
+ // en un handler:
38
+ return TypedResults.Problem(title: "Order not found", statusCode: StatusCodes.Status404NotFound);
39
+ ```
40
+
41
+ ## 9.4 Correct Status Codes via TypedResults — MEDIUM
42
+
43
+ `TypedResults` devuelve el status code correcto de forma tipada y mejora los metadatos de OpenAPI.
44
+
45
+ **Correcto:**
46
+ ```csharp
47
+ app.MapPost("/orders", async (CreateOrder cmd, IOrderService svc) =>
48
+ {
49
+ var order = await svc.CreateAsync(cmd);
50
+ return TypedResults.Created($"/orders/{order.Id}", order); // 201 + Location
51
+ });
52
+ ```
53
+
54
+ ## 9.5 Pagination & Idempotency for Writes — MEDIUM
55
+
56
+ Paginar colecciones evita respuestas ilimitadas; una Idempotency-Key hace que un POST repetido (reintento de red) no duplique el recurso.
57
+
58
+ **Correcto:**
59
+ ```csharp
60
+ app.MapGet("/orders", (int page = 1, int pageSize = 20) => /* Skip/Take con límites */ );
61
+ app.MapPost("/payments", ([FromHeader(Name = "Idempotency-Key")] string key, Payment p) =>
62
+ /* deduplicar por key antes de procesar */ );
63
+ ```
64
+
65
+ ## 9.6 Built-in OpenAPI — LOW
66
+
67
+ `Microsoft.AspNetCore.OpenApi` genera el documento OpenAPI sin dependencias externas.
68
+
69
+ **Correcto:**
70
+ ```csharp
71
+ builder.Services.AddOpenApi();
72
+ app.MapOpenApi(); // /openapi/v1.json
73
+ ```
74
+
75
+ _Ref: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/apiversioning · https://learn.microsoft.com/en-us/aspnet/core/web-api/handle-errors · https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/overview · https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/responses_
@@ -0,0 +1,62 @@
1
+ # 1. Architecture (CRITICAL)
2
+
3
+ ## 1.1 Organize by Feature, Not Technical Layers — CRITICAL
4
+
5
+ Carpetas por feature (vertical slice) → cohesión alta y cambios localizados; las carpetas por capa técnica dispersan un cambio en 5 sitios.
6
+
7
+ **Incorrecto** — carpetas técnicas globales:
8
+ ```csharp
9
+ // Controllers/, Services/, Repositories/, Dtos/ ... un cambio toca 4 carpetas
10
+ ```
11
+ **Correcto** — un slice por feature:
12
+ ```csharp
13
+ // Features/Orders/{ CreateOrder.cs, GetOrder.cs, OrdersEndpoints.cs, OrderDto.cs }
14
+ // Features/Users/{ RegisterUser.cs, UsersEndpoints.cs, UserDto.cs }
15
+ ```
16
+
17
+ ## 1.2 Thin Endpoints — CRITICAL
18
+
19
+ El endpoint parsea, delega y forma la respuesta. Sin lógica de negocio: así es testeable y reutilizable desde otros transportes.
20
+
21
+ **Incorrecto:**
22
+ ```csharp
23
+ app.MapPost("/orders", async (CreateOrderDto dto, AppDbContext db) =>
24
+ {
25
+ if (dto.Items.Count == 0) return Results.BadRequest(); // negocio en el endpoint
26
+ var total = dto.Items.Sum(i => i.Price * i.Qty); // negocio en el endpoint
27
+ db.Orders.Add(new Order { Total = total });
28
+ await db.SaveChangesAsync();
29
+ return Results.Ok();
30
+ });
31
+ ```
32
+ **Correcto:**
33
+ ```csharp
34
+ app.MapPost("/orders", async (CreateOrderRequest req, CreateOrderHandler handler, CancellationToken ct) =>
35
+ {
36
+ var result = await handler.HandleAsync(req, ct);
37
+ return result.IsSuccess ? Results.Created($"/orders/{result.Value.Id}", result.Value) : result.ToProblem();
38
+ });
39
+ ```
40
+
41
+ ## 1.3 Dependencies Point Inward / Slices Independent — HIGH
42
+
43
+ El dominio no conoce infraestructura (Clean Architecture): el handler depende de una abstracción `IOrderRepository`, no de `AppDbContext`. Entre slices, sin acoplamiento directo; coordinar vía eventos de dominio o un mediador.
44
+
45
+ ## 1.4 Single Responsibility per Handler — HIGH
46
+
47
+ Un handler resuelve un caso de uso. `CreateOrderHandler` no envía emails ni cobra: emite un evento y otro handler reacciona. Mejora testabilidad y limita el blast radius de cada cambio.
48
+
49
+ ## 1.5 Program.cs as Composition Root Only — MEDIUM-HIGH
50
+
51
+ `Program.cs` solo registra servicios y arma el pipeline; nada de lógica de negocio. Mover el wiring a extensiones (`builder.Services.AddOrdersFeature()`) mantiene el root legible.
52
+
53
+ ```csharp
54
+ var builder = WebApplication.CreateBuilder(args);
55
+ builder.Services.AddOrdersFeature().AddAuthFeature();
56
+ var app = builder.Build();
57
+ app.UseExceptionHandler().UseAuthorization();
58
+ app.MapOrdersEndpoints();
59
+ app.Run();
60
+ ```
61
+
62
+ _Ref: https://learn.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/common-web-application-architectures · https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/min-api-filters_
@@ -0,0 +1,62 @@
1
+ # 3. Async & Concurrency (HIGH)
2
+
3
+ ## 3.1 Async All the Way — CRITICAL
4
+
5
+ `.Result`, `.Wait()` y `.GetAwaiter().GetResult()` bloquean el hilo: deadlocks y thread-pool starvation bajo carga. Propagar `async`/`await` de punta a punta.
6
+
7
+ **Incorrecto:**
8
+ ```csharp
9
+ public Order Get(int id) => repo.GetAsync(id).Result; // bloquea el thread-pool
10
+ ```
11
+ **Correcto:**
12
+ ```csharp
13
+ public async Task<Order> GetAsync(int id, CancellationToken ct) => await repo.GetAsync(id, ct);
14
+ ```
15
+
16
+ ## 3.2 Accept and Propagate CancellationToken — HIGH
17
+
18
+ El token aborta el trabajo cuando el cliente corta la conexión, liberando hilos y conexiones de DB. Pasarlo a cada llamada async, hasta EF Core y `HttpClient`.
19
+
20
+ **Correcto:**
21
+ ```csharp
22
+ app.MapGet("/orders/{id:int}", async (int id, AppDbContext db, CancellationToken ct) =>
23
+ await db.Orders.FirstOrDefaultAsync(o => o.Id == id, ct) is { } o ? Results.Ok(o) : Results.NotFound());
24
+ ```
25
+
26
+ ## 3.3 No async void — HIGH
27
+
28
+ `async void` no se puede esperar y sus excepciones tumban el proceso. Usar `async Task`. La única excepción son los event handlers de UI/eventos.
29
+
30
+ **Incorrecto:**
31
+ ```csharp
32
+ public async void Process() => await DoWorkAsync(); // excepción => crash no observable
33
+ ```
34
+ **Correcto:**
35
+ ```csharp
36
+ public async Task ProcessAsync(CancellationToken ct) => await DoWorkAsync(ct);
37
+ ```
38
+
39
+ ## 3.4 ConfigureAwait(false) in Libraries — MEDIUM-HIGH
40
+
41
+ En código de librería reutilizable, `ConfigureAwait(false)` evita volver al contexto capturado y previene deadlocks en hosts con `SynchronizationContext`. En apps ASP.NET Core (sin contexto) no hace falta.
42
+
43
+ ```csharp
44
+ var data = await httpClient.GetStringAsync(url, ct).ConfigureAwait(false);
45
+ ```
46
+
47
+ ## 3.5 Task.WhenAll for Parallel Independent Work — MEDIUM-HIGH
48
+
49
+ Operaciones independientes en paralelo en vez de secuencial. No compartir un `DbContext` entre tasks concurrentes (no es thread-safe): usar un scope/contexto por task.
50
+
51
+ ```csharp
52
+ var (user, prefs) = (await Task.WhenAll(GetUserAsync(id, ct), GetPrefsAsync(id, ct))) switch
53
+ {
54
+ var r => (r[0], r[1])
55
+ };
56
+ ```
57
+
58
+ ## 3.6 No Fire-and-Forget Without Error Handling — HIGH
59
+
60
+ Un `Task` descartado traga excepciones y puede morir al reciclar el proceso. Para trabajo en background usar un `BackgroundService` / cola, con logging del error.
61
+
62
+ _Ref: https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/async-scenarios · https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/consuming-the-task-based-asynchronous-pattern_
@@ -0,0 +1,69 @@
1
+ # 7. Database & EF Core (MEDIUM-HIGH)
2
+
3
+ ## 7.1 Avoid N+1 with Include or Projection — HIGH
4
+
5
+ Acceder a relaciones en un loop dispara una query por fila. Usar `Include` o proyectar a un DTO con `Select` resuelve todo en una sola query.
6
+
7
+ **Incorrecto:**
8
+ ```csharp
9
+ var orders = await db.Orders.ToListAsync();
10
+ foreach (var o in orders) o.Customer = await db.Customers.FindAsync(o.CustomerId); // N+1
11
+ ```
12
+ **Correcto:**
13
+ ```csharp
14
+ var orders = await db.Orders
15
+ .Select(o => new OrderDto(o.Id, o.Customer.Name, o.Total))
16
+ .ToListAsync();
17
+ ```
18
+
19
+ ## 7.2 AsNoTracking for Read-Only Queries — HIGH
20
+
21
+ El change tracker añade overhead innecesario en lecturas. `AsNoTracking()` evita las snapshots y reduce allocations.
22
+
23
+ **Correcto:**
24
+ ```csharp
25
+ var products = await db.Products.AsNoTracking().Where(p => p.Active).ToListAsync();
26
+ ```
27
+
28
+ ## 7.3 DbContext is the Unit of Work — MEDIUM-HIGH
29
+
30
+ `DbContext` ya implementa Unit of Work + Repository. Envolverlo en un repositorio genérico oculta `Include`, proyecciones y `SaveChanges`, añadiendo abstracción sin valor.
31
+
32
+ **Incorrecto:**
33
+ ```csharp
34
+ public class GenericRepository<T> { Task<T> GetById(int id); /* envuelve DbSet sin necesidad */ }
35
+ ```
36
+ **Correcto:**
37
+ ```csharp
38
+ // Inyectar AppDbContext (o un servicio de dominio que lo use) directamente
39
+ public sealed class OrderService(AppDbContext db) { /* usa db.Orders, db.SaveChangesAsync */ }
40
+ ```
41
+
42
+ ## 7.4 Versioned Migrations, Never EnsureCreated in Prod — MEDIUM-HIGH
43
+
44
+ `EnsureCreated()` no usa migraciones y no es upgradeable: hay que migrar el schema versionado con la CLI de EF.
45
+
46
+ **Incorrecto:**
47
+ ```csharp
48
+ await db.Database.EnsureCreatedAsync(); // no migrable, pierde historial de schema
49
+ ```
50
+ **Correcto:**
51
+ ```bash
52
+ dotnet ef migrations add AddOrderIndex
53
+ dotnet ef database update # en deploy: db.Database.MigrateAsync()
54
+ ```
55
+
56
+ ## 7.5 Connection Resiliency & Split Queries — MEDIUM
57
+
58
+ Habilitar reintentos ante fallos transitorios y `AsSplitQuery()` para `Include` grandes evita la explosión cartesiana de un único JOIN.
59
+
60
+ **Correcto:**
61
+ ```csharp
62
+ builder.Services.AddDbContext<AppDbContext>(o =>
63
+ o.UseNpgsql(conn, npgsql => npgsql.EnableRetryOnFailure()));
64
+
65
+ var blogs = await db.Blogs.Include(b => b.Posts).Include(b => b.Tags)
66
+ .AsSplitQuery().ToListAsync();
67
+ ```
68
+
69
+ _Ref: https://learn.microsoft.com/en-us/ef/core/querying/related-data/eager-loading · https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations · https://learn.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency_
@@ -0,0 +1,73 @@
1
+ # 2. Dependency Injection (CRITICAL)
2
+
3
+ ## 2.1 Prefer Constructor Injection (Primary Constructors) — CRITICAL
4
+
5
+ Dependencias explícitas, type-safe y testeables. Con primary constructors (C# 12+) el boilerplate desaparece.
6
+
7
+ **Correcto:**
8
+ ```csharp
9
+ public sealed class CreateOrderHandler(IOrderRepository repo, ILogger<CreateOrderHandler> logger)
10
+ {
11
+ public async Task HandleAsync(CreateOrderRequest req, CancellationToken ct)
12
+ {
13
+ logger.LogInformation("Creating order for {Customer}", req.CustomerId);
14
+ await repo.AddAsync(req.ToOrder(), ct);
15
+ }
16
+ }
17
+ ```
18
+
19
+ ## 2.2 Use Correct Lifetimes — CRITICAL
20
+
21
+ `Scoped` para `DbContext` y handlers (una instancia por request). `Singleton` solo para servicios stateless y thread-safe. `Transient` para servicios livianos sin estado.
22
+
23
+ ```csharp
24
+ builder.Services.AddDbContext<AppDbContext>(o => o.UseNpgsql(cs)); // Scoped por defecto
25
+ builder.Services.AddScoped<CreateOrderHandler>();
26
+ builder.Services.AddSingleton<IClock, SystemClock>(); // stateless
27
+ ```
28
+
29
+ ## 2.3 Never Inject Scoped into Singleton — CRITICAL
30
+
31
+ Es la "captured dependency": el Singleton retiene una instancia Scoped muerta tras el primer request → datos corruptos o `ObjectDisposedException`. Resolverlo bajo demanda con un scope.
32
+
33
+ **Incorrecto:**
34
+ ```csharp
35
+ public sealed class CacheWarmer(AppDbContext db) : IHostedService { } // Singleton captura DbContext Scoped
36
+ ```
37
+ **Correcto:**
38
+ ```csharp
39
+ public sealed class CacheWarmer(IServiceScopeFactory scopeFactory) : IHostedService
40
+ {
41
+ public async Task StartAsync(CancellationToken ct)
42
+ {
43
+ await using var scope = scopeFactory.CreateAsyncScope();
44
+ var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
45
+ }
46
+ }
47
+ ```
48
+
49
+ ## 2.4 No Service Locator / No Manual BuildServiceProvider() — HIGH
50
+
51
+ `provider.GetService<T>()` dentro de la lógica oculta dependencias y rompe los tests. `BuildServiceProvider()` en `Program.cs` crea un container paralelo (singletons duplicados, fugas). Inyectar por constructor (2.1).
52
+
53
+ ## 2.5 Register by Interface — HIGH
54
+
55
+ Registrar contra la abstracción permite sustituir implementaciones (tests, features) sin tocar consumidores.
56
+
57
+ ```csharp
58
+ builder.Services.AddScoped<IOrderRepository, EfOrderRepository>();
59
+ ```
60
+
61
+ ## 2.6 IOptions<T> Pattern for Config — MEDIUM-HIGH
62
+
63
+ Bind tipado y validado de configuración, en vez de leer `IConfiguration` con strings por todo el código.
64
+
65
+ ```csharp
66
+ builder.Services.AddOptions<JwtOptions>()
67
+ .Bind(builder.Configuration.GetSection("Jwt"))
68
+ .ValidateDataAnnotations()
69
+ .ValidateOnStart();
70
+ // constructor(IOptions<JwtOptions> options) => _opts = options.Value;
71
+ ```
72
+
73
+ _Ref: https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection · https://learn.microsoft.com/en-us/dotnet/core/extensions/options_
@@ -0,0 +1,76 @@
1
+ # 10. DevOps & Deployment (LOW-MEDIUM)
2
+
3
+ ## 10.1 Multi-Stage Dockerfile, Non-Root User — MEDIUM
4
+
5
+ Compilar con la imagen SDK y correr sobre la imagen `aspnet` runtime reduce el tamaño y la superficie de ataque. Usar un usuario no-root limita el blast radius.
6
+
7
+ **Correcto:**
8
+ ```dockerfile
9
+ FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
10
+ WORKDIR /src
11
+ COPY . .
12
+ RUN dotnet publish -c Release -o /app
13
+
14
+ FROM mcr.microsoft.com/dotnet/aspnet:10.0
15
+ WORKDIR /app
16
+ COPY --from=build /app .
17
+ USER $APP_UID
18
+ ENTRYPOINT ["dotnet", "Api.dll"]
19
+ ```
20
+
21
+ ## 10.2 Health Checks (Liveness/Readiness) — MEDIUM
22
+
23
+ Los orquestadores (Kubernetes) necesitan endpoints separados: liveness (¿está vivo?) y readiness (¿puede recibir tráfico?, p. ej. DB conectada).
24
+
25
+ **Correcto:**
26
+ ```csharp
27
+ builder.Services.AddHealthChecks().AddNpgSql(conn, tags: ["ready"]);
28
+ app.MapHealthChecks("/health/live", new() { Predicate = _ => false });
29
+ app.MapHealthChecks("/health/ready", new() { Predicate = c => c.Tags.Contains("ready") });
30
+ ```
31
+
32
+ ## 10.3 Structured Logging (Serilog JSON) — MEDIUM
33
+
34
+ Logs en JSON son parseables por agregadores (Loki, ELK). Serilog con sink de consola en formato compacto facilita la observabilidad.
35
+
36
+ **Correcto:**
37
+ ```csharp
38
+ builder.Host.UseSerilog((ctx, cfg) =>
39
+ cfg.WriteTo.Console(new Serilog.Formatting.Compact.CompactJsonFormatter()));
40
+ ```
41
+
42
+ ## 10.4 OpenTelemetry for Traces & Metrics — MEDIUM
43
+
44
+ OpenTelemetry instrumenta traces y métricas de forma estándar y exportable a cualquier backend OTLP.
45
+
46
+ **Correcto:**
47
+ ```csharp
48
+ builder.Services.AddOpenTelemetry()
49
+ .WithTracing(t => t.AddAspNetCoreInstrumentation().AddOtlpExporter())
50
+ .WithMetrics(m => m.AddAspNetCoreInstrumentation().AddOtlpExporter());
51
+ ```
52
+
53
+ ## 10.5 Config per Environment & Graceful Shutdown — LOW-MEDIUM
54
+
55
+ Nunca hardcodear secrets ni connection strings: usar `appsettings.{Environment}.json` y variables de entorno. Registrar limpieza ante el shutdown con `IHostApplicationLifetime`.
56
+
57
+ **Correcto:**
58
+ ```csharp
59
+ var conn = builder.Configuration.GetConnectionString("Default"); // env / appsettings, no hardcoded
60
+ app.Lifetime.ApplicationStopping.Register(() => Log.Information("Draining connections..."));
61
+ ```
62
+
63
+ ## 10.6 CI: build, test, format — LOW
64
+
65
+ El pipeline debe fallar ante código que no compila, tests rojos o formato inconsistente.
66
+
67
+ **Correcto:**
68
+ ```yaml
69
+ steps:
70
+ - run: dotnet restore
71
+ - run: dotnet build --no-restore -c Release
72
+ - run: dotnet test --no-build -c Release
73
+ - run: dotnet format --verify-no-changes
74
+ ```
75
+
76
+ _Ref: https://learn.microsoft.com/en-us/dotnet/core/docker/build-container · https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks · https://learn.microsoft.com/en-us/dotnet/core/diagnostics/observability-with-otel · https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host_
@@ -0,0 +1,72 @@
1
+ # 4. Error Handling (HIGH)
2
+
3
+ ## 4.1 Global IExceptionHandler + ProblemDetails — CRITICAL
4
+
5
+ Respuestas de error consistentes y estandarizadas (RFC 7807) sin try/catch repetido en cada endpoint. Registrar `AddProblemDetails()` y un `IExceptionHandler`.
6
+
7
+ **Correcto:**
8
+ ```csharp
9
+ public sealed class GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger) : IExceptionHandler
10
+ {
11
+ public async ValueTask<bool> TryHandleAsync(HttpContext ctx, Exception ex, CancellationToken ct)
12
+ {
13
+ logger.LogError(ex, "Unhandled exception on {Path}", ctx.Request.Path);
14
+ await Results.Problem(statusCode: StatusCodes.Status500InternalServerError, title: "An error occurred")
15
+ .ExecuteAsync(ctx);
16
+ return true;
17
+ }
18
+ }
19
+ // Program.cs
20
+ builder.Services.AddProblemDetails();
21
+ builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
22
+ app.UseExceptionHandler();
23
+ ```
24
+
25
+ ## 4.2 Don't Leak Stack Traces — CRITICAL
26
+
27
+ En producción no exponer `ex.Message`, stack traces ni detalles internos: filtran rutas, versiones y vectores de ataque. Loggear el detalle, devolver un mensaje genérico (ver 4.1).
28
+
29
+ ## 4.3 Result Pattern for Expected Failures — HIGH
30
+
31
+ Fallos esperados (validación, "not found", reglas de negocio) no son excepciones: las excepciones son caras y para lo excepcional. Devolver un `Result`.
32
+
33
+ **Incorrecto:**
34
+ ```csharp
35
+ if (order is null) throw new NotFoundException(); // control de flujo con excepciones
36
+ ```
37
+ **Correcto:**
38
+ ```csharp
39
+ public readonly record struct Result<T>(bool IsSuccess, T? Value, string? Error)
40
+ {
41
+ public static Result<T> Ok(T value) => new(true, value, null);
42
+ public static Result<T> Fail(string error) => new(false, default, error);
43
+ }
44
+ return order is null ? Result<Order>.Fail("Order not found") : Result<Order>.Ok(order);
45
+ ```
46
+
47
+ ## 4.4 Validation → ValidationProblemDetails — HIGH
48
+
49
+ Los errores de validación se devuelven como `ValidationProblemDetails` (400) con el mapa campo→errores, no como 500.
50
+
51
+ ```csharp
52
+ return Results.ValidationProblem(new Dictionary<string, string[]>
53
+ {
54
+ ["email"] = ["Email is required"]
55
+ });
56
+ ```
57
+
58
+ ## 4.5 No Empty Catch; Log With Context — MEDIUM-HIGH
59
+
60
+ `catch {}` oculta fallos y vuelve la app indebuggeable. Capturar tipos específicos, loggear con structured logging y re-lanzar o devolver un `Result`.
61
+
62
+ **Incorrecto:**
63
+ ```csharp
64
+ try { await Pay(ct); } catch { } // se traga el error
65
+ ```
66
+ **Correcto:**
67
+ ```csharp
68
+ try { await Pay(ct); }
69
+ catch (PaymentException ex) { logger.LogError(ex, "Payment failed for {OrderId}", orderId); throw; }
70
+ ```
71
+
72
+ _Ref: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling · https://learn.microsoft.com/en-us/aspnet/core/web-api/handle-errors_