# Regla: Estilo de Código — C# / .NET

Aplica a todo código C# del proyecto. Estas reglas aprovechan las características
modernas de C# (10+) y .NET 6+ para escribir código más expresivo, seguro y con
menos ruido sintáctico. El objetivo es código que el IDE y el compilador puedan
analizar estáticamente con la mayor cobertura posible.

---

## dotnet format (obligatorio)

- `dotnet format` es el formateador oficial y su uso es obligatorio.
- Verificar sin modificar: `dotnet format --verify-no-changes`
- CI falla si `dotnet format --verify-no-changes` detecta diferencias.
- El archivo `.editorconfig` en la raíz del proyecto define las reglas de formato.
  Versionar `.editorconfig` junto con el código.
- NUNCA editar manualmente el indentado — dejar que `dotnet format` decida.

---

## Nullable reference types (obligatorio)

- Habilitar nullable reference types en todos los proyectos nuevos:
  ```xml
  <!-- En el .csproj -->
  <Nullable>enable</Nullable>
  ```
- O a nivel de archivo: `#nullable enable` al inicio del archivo.
- El compilador emite warnings cuando un nullable puede ser null sin verificación.
  Tratar estos warnings como errores en CI:
  ```xml
  <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  ```
- No usar `!` (null-forgiving operator) para suprimir el warning sin verificar:
  ```csharp
  // MAL — suprime el warning sin garantizar que no es null
  string nombre = usuario.Nombre!;

  // BIEN — verificar antes de usar
  string nombre = usuario.Nombre ?? throw new InvalidOperationException("Nombre requerido");
  ```

---

## Records para DTOs inmutables

```csharp
// MAL — clase mutable con boilerplate
public class FacturaDto
{
    public Guid Id { get; set; }
    public decimal Total { get; set; }
    public string ClienteEmail { get; set; } = string.Empty;
}

// BIEN — record inmutable, con/without para crear variantes
public record FacturaDto(
    Guid Id,
    decimal Total,
    string ClienteEmail
);

// Crear variante modificada sin mutar el original
var actualizada = factura with { Total = 1500.0m };

// record struct para tipos de valor pequeños (sin heap allocation)
public record struct Coordenadas(double Latitud, double Longitud);
```

- Usar `record class` (o simplemente `record`) para DTOs de respuesta y eventos.
- Usar `record struct` para tipos de valor pequeños e inmutables.
- Los records implementan `Equals`, `GetHashCode` y `ToString` automáticamente.

---

## Pattern matching con switch expressions

```csharp
// MAL — switch statement verboso
string DescribirEstatus(EstadoPedido estado)
{
    switch (estado)
    {
        case EstadoPedido.Pendiente: return "En espera de confirmación";
        case EstadoPedido.Confirmado: return "Confirmado, preparando envío";
        case EstadoPedido.Enviado: return "En camino";
        default: return "Estado desconocido";
    }
}

// BIEN — switch expression conciso
string DescribirEstatus(EstadoPedido estado) => estado switch
{
    EstadoPedido.Pendiente   => "En espera de confirmación",
    EstadoPedido.Confirmado  => "Confirmado, preparando envío",
    EstadoPedido.Enviado     => "En camino",
    _                        => throw new ArgumentOutOfRangeException(nameof(estado))
};
```

- Usar switch expressions para mapeos exhaustivos — el compilador detecta casos no cubiertos.
- Usar `_` como default solo cuando hay un caso genuinamente inesperado (y lanzar excepción).
- Pattern matching con `is`, `when` y destructuring para condiciones complejas.

---

## async/await: siempre propagar CancellationToken

```csharp
// MAL — no soporta cancelación
public async Task<Factura> ObtenerAsync(Guid id)
{
    return await _repo.ObtenerAsync(id);
}

// BIEN — permite al llamador cancelar la operación
public async Task<Factura> ObtenerAsync(Guid id, CancellationToken ct = default)
{
    return await _repo.ObtenerAsync(id, ct);
}
```

- `CancellationToken` en todos los métodos `async` públicos — siempre como último parámetro.
- Parámetro con valor por defecto `= default` para no romper llamadores existentes.
- NUNCA usar `Task.Result` ni `Task.Wait()` — bloquean el hilo y causan deadlocks en ASP.NET.
- NUNCA usar `async void` — solo `async Task` o `async Task<T>`.
  Excepción: event handlers de UI (WinForms/WPF) donde el tipo está forzado.

---

## Convenciones de nombres

| Elemento | Convención | Ejemplo |
|----------|-----------|---------|
| Clases, records, interfaces | `PascalCase` | `FacturaService`, `IRepositorio` |
| Métodos y propiedades públicas | `PascalCase` | `ObtenerFactura()`, `TotalImpuestos` |
| Parámetros y variables locales | `camelCase` | `facturaId`, `totalCalculado` |
| Campos privados | `_camelCase` (prefijo guion bajo) | `_repositorio`, `_logger` |
| Constantes | `PascalCase` (no SCREAMING) | `MaxReintentos`, `TiempoEsperaMs` |
| Interfaces | `I` + `PascalCase` | `IFacturaRepository`, `IEmailService` |
| Parámetros genéricos | `T`, `TKey`, `TValue`, o nombre descriptivo | `TEntidad`, `TResultado` |

- NUNCA prefijo `m_` ni `s_` para campos — son convenciones de C++ que no aplican.
- Las interfaces siempre empiezan con `I` — es una convención universal en C#.

---

## Using declarations para IDisposable

```csharp
// MAL — bloque using con llaves innecesarias
using (var conexion = new SqlConnection(connectionString))
{
    using (var comando = conexion.CreateCommand())
    {
        // ... código ...
    }
}

// BIEN — using declaration sin llaves (C# 8+)
using var conexion = new SqlConnection(connectionString);
using var comando = conexion.CreateCommand();
// ... código ...
// Dispose se llama automáticamente al final del scope del método
```

- `using var` para todos los `IDisposable` — el compilador inserta el `Dispose()`.
- El scope es el método completo — si se necesita scope más corto, usar el bloque `using { }`.

---

## File-scoped namespaces y top-level statements

```csharp
// MAL — namespace con bloque (C# antiguo)
namespace MiProyecto.Facturacion
{
    public class FacturaService { ... }
}

// BIEN — file-scoped namespace (C# 10+, reduce un nivel de indentación)
namespace MiProyecto.Facturacion;

public class FacturaService { ... }
```

- File-scoped namespaces en todos los archivos nuevos.
- Top-level statements solo en `Program.cs` — no en clases de negocio.

---

## LINQ: method syntax preferido

```csharp
// MAL — query syntax (más verboso, mezcla SQL con C#)
var facturasPagadas = from f in facturas
                      where f.Estatus == EstadoFactura.Pagada
                      orderby f.FechaPago descending
                      select f;

// BIEN — method syntax (consistente con el resto de C# moderno)
var facturasPagadas = facturas
    .Where(f => f.Estatus == EstadoFactura.Pagada)
    .OrderByDescending(f => f.FechaPago);
```

- Method syntax en todo el código nuevo — es más consistente con el estilo moderno.
- Query syntax solo cuando mejora la legibilidad en joins complejos (caso raro).
- NUNCA LINQ en bucles críticos de performance — medir antes de optimizar.

---

## Longitud máxima de método: 40 líneas

- Los métodos no deben exceder 40 líneas de código efectivo.
- Si un método supera 40 líneas: extraer métodos privados con nombres descriptivos.
- Constructores con mucha inicialización: considerar el patrón Factory.

---

## Checklist de estilo antes de abrir PR

- [ ] `dotnet format --verify-no-changes` pasa sin diferencias
- [ ] Nullable reference types habilitados y sin `!` sin justificación
- [ ] DTOs inmutables definidos como `record`
- [ ] Todos los métodos `async` reciben `CancellationToken`
- [ ] Sin `Task.Result` ni `Task.Wait()` en código async
- [ ] Sin `async void` fuera de event handlers
- [ ] File-scoped namespaces en archivos nuevos
- [ ] LINQ en method syntax
- [ ] Métodos <= 40 líneas
