# Regla: Hooks de Calidad — C# / .NET

Checks automáticos que se ejecutan en puntos clave del ciclo de desarrollo para
detectar problemas antes de que lleguen al equipo o a producción. Todos los checks
son obligatorios y bloquean el flujo si detectan un problema.

---

## dotnet format --verify-no-changes antes de commit

**Pre-commit hook** — bloquea el commit si el código no está formateado:

```bash
#!/usr/bin/env bash
# .git/hooks/pre-commit

set -e

echo "Verificando formato con dotnet format..."
dotnet format --verify-no-changes

if [ $? -ne 0 ]; then
    echo "ERROR: El código no está formateado."
    echo "Ejecutar: dotnet format"
    exit 1
fi
```

Con el framework `pre-commit`:

```yaml
# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: dotnet-format
        name: dotnet format
        language: system
        entry: dotnet format --verify-no-changes
        pass_filenames: false
        types: [c#]
```

- Si el check falla: ejecutar `dotnet format` sin flags y hacer el commit de nuevo.
- El archivo `.editorconfig` es la fuente de verdad para las reglas de formato.
- NUNCA commitear con `--no-verify` para saltarse el formato.

---

## dotnet build antes de commit

**Pre-commit hook** — bloquea el commit si hay errores de compilación:

```bash
echo "Compilando el proyecto..."
dotnet build --no-restore -warnaserror

if [ $? -ne 0 ]; then
    echo "ERROR: La compilación falló."
    echo "Corregir los errores antes de hacer commit."
    exit 1
fi
```

- `--no-restore` en el hook — el restore es costoso y debe hacerse manualmente.
- `-warnaserror` convierte warnings en errores — la misma configuración que CI.
- El flag `-warnaserror` debe estar también en el `.csproj` para consistencia:
  ```xml
  <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  ```

---

## Roslyn analyzers habilitados como errores

Los analyzers de Roslyn detectan bugs y problemas de seguridad durante la compilación.
Configurar en el `.csproj`:

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <!-- Nullable reference types como errores -->
    <Nullable>enable</Nullable>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>

    <!-- Nivel de análisis máximo -->
    <AnalysisLevel>latest</AnalysisLevel>
    <AnalysisMode>All</AnalysisMode>

    <!-- Security analyzers -->
    <EnableNETAnalyzers>true</EnableNETAnalyzers>
  </PropertyGroup>

  <ItemGroup>
    <!-- Analyzers adicionales de seguridad -->
    <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" />
    <PackageReference Include="SecurityCodeScan.VS2019" PrivateAssets="all" />
    <PackageReference Include="Meziantou.Analyzer" PrivateAssets="all" />
  </ItemGroup>
</Project>
```

- `SecurityCodeScan` detecta SQL injection, XSS y otras vulnerabilidades OWASP en tiempo de compilación.
- `Meziantou.Analyzer` detecta patrones problemáticos de async/await, performance y API misuse.
- Si un analyzer genera un falso positivo: suprimir con `#pragma warning disable CSXXXX`
  en el sitio exacto, con comentario explicando por qué.
  NUNCA suprimir a nivel de proyecto sin revisión del equipo.

---

## Detección de Console.WriteLine en código no-test

**En CI como parte del build** — detecta logs de debug que no deben llegar a producción:

```yaml
# .github/workflows/ci.yml
- name: Verificar ausencia de Console.WriteLine en producción
  run: |
    # Buscar en src/ pero no en archivos de test
    if grep -rn "Console\.Write" src/ --include="*.cs" | \
       grep -v "\.Tests\." | grep -v "// ALLOW-CONSOLE"; then
      echo "ERROR: Console.Write encontrado en código de producción."
      echo "Usar ILogger<T> en su lugar."
      exit 1
    fi
```

O mediante un analyzer de Roslyn configurado en `.editorconfig`:

```ini
# .editorconfig
[*.cs]
# Prohibir Console.Write* en código de producción
dotnet_diagnostic.CA2241.severity = error

# Configurar analyzer para detectar Console.WriteLine
dotnet_diagnostic.MA0003.severity = error  # Meziantou: evitar Console.Write
```

- `Console.WriteLine` y `Console.Write` permitidos solo en:
  - Proyectos de tipo CLI/consola donde es el output intencionado
  - Archivos `*.Tests.cs` o dentro de proyectos `*.Tests`
  - Código marcado con `// ALLOW-CONSOLE: razón específica`
- En el resto del código: usar `ILogger<T>` inyectado via DI.

---

## dotnet test con cobertura en CI

```yaml
# .github/workflows/ci.yml
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.x'

      - name: Restaurar dependencias
        run: dotnet restore

      - name: Compilar
        run: dotnet build --no-restore -warnaserror

      - name: Ejecutar tests con cobertura
        run: |
          dotnet test --no-build \
            --collect:"XPlat Code Coverage" \
            --results-directory ./coverage

      - name: Verificar umbral de cobertura
        run: |
          dotnet tool install -g dotnet-reportgenerator-globaltool
          reportgenerator \
            -reports:"./coverage/**/*.xml" \
            -targetdir:./coverage-report \
            -reporttypes:Cobertura
          # Falla si cobertura total < 80%
          python scripts/check-coverage.py ./coverage-report/Cobertura.xml 80

      - name: Publicar reporte de cobertura
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: ./coverage-report
```

- `dotnet test` en CI con `--collect:"XPlat Code Coverage"` genera reportes Cobertura XML.
- Publicar el reporte como artefacto del job para revisión post-ejecución.
- Cobertura mínima: 80% para proyectos de lógica de negocio.

---

## Security analyzers integrados

```yaml
# Job de seguridad en CI — separado del job de tests
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Verificar vulnerabilidades en paquetes
        run: dotnet list package --vulnerable --include-transitive

      - name: Ejecutar CodeQL
        uses: github/codeql-action/analyze@v3
        with:
          languages: csharp

      - name: Ejecutar OWASP Dependency Check
        uses: dependency-check/Dependency-Check_Action@main
        with:
          format: HTML
          args: >
            --scan . --out reports
            --failOnCVSS 7
```

- `dotnet list package --vulnerable` detecta NuGet packages con CVEs conocidos.
- CodeQL analiza el código en busca de vulnerabilidades de seguridad.
- OWASP Dependency Check como capa adicional para detección de CVEs.
- El job de seguridad corre en cada PR y semanalmente como job independiente.

---

## Configuracion completa del pipeline CI

```yaml
# .github/workflows/ci.yml — pipeline completo
name: CI

on: [push, pull_request]

jobs:
  format:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with: { dotnet-version: '8.x' }
      - run: dotnet format --verify-no-changes

  build-and-test:
    runs-on: ubuntu-latest
    needs: format
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with: { dotnet-version: '8.x' }
      - run: dotnet restore
      - run: dotnet build --no-restore -warnaserror
      - run: dotnet test --no-build --collect:"XPlat Code Coverage"

  security:
    runs-on: ubuntu-latest
    needs: build-and-test
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with: { dotnet-version: '8.x' }
      - run: dotnet list package --vulnerable --include-transitive
```

---

## Checklist de hooks antes de integrar al proyecto

- [ ] Hook de pre-commit instalado con `dotnet format --verify-no-changes`
- [ ] Hook de pre-commit con `dotnet build -warnaserror`
- [ ] `<TreatWarningsAsErrors>true</TreatWarningsAsErrors>` en todos los `.csproj`
- [ ] `<Nullable>enable</Nullable>` en todos los `.csproj`
- [ ] Roslyn analyzers configurados (`SecurityCodeScan`, `Meziantou.Analyzer`)
- [ ] Pipeline de CI con job de formato, compilación, tests y seguridad
- [ ] `dotnet test` con cobertura en CI y verificación del umbral del 80%
- [ ] `dotnet list package --vulnerable` en CI (cada PR y semanalmente)
- [ ] Detección de `Console.WriteLine` en código de producción
