Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 1 addition & 40 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,40 +1 @@
# CODEOWNERS
#
# Define quién debe revisar cambios en partes específicas del repositorio.
# GitHub solicita automáticamente la revisión de los owners cuando un PR
# modifica archivos que coinciden con algún patrón.
#
# Sintaxis: patrón @usuario-o-equipo
#
# Las reglas se evalúan de arriba hacia abajo.
# La última regla que coincida con un archivo gana.
# Los patrones siguen la misma sintaxis que .gitignore.

# --- Owner global ---
# Aplica a todo lo que no tenga una regla más específica abajo.
* @TU_USUARIO

# --- Librería principal ---
# Cualquier cambio en el código fuente requiere revisión de los maintainers.
/src/ @TU_USUARIO

# --- Tests ---
# Los tests pueden revisarlos los mismos developers, no solo maintainers.
# En un equipo real aquí irían @equipo/developers o usuarios individuales.
/tests/ @TU_USUARIO

# --- Workflows de CI/CD ---
# Los cambios en pipelines tienen un impacto alto: pueden exponer secrets,
# modificar qué checks son requeridos, o cambiar tiempos de build.
# Requieren revisión explícita de quien administra la infraestructura.
/.github/workflows/ @TU_USUARIO

# --- Configuración de seguridad y políticas ---
# CODEOWNERS mismo, editorconfig y configuración del repo.
/.github/CODEOWNERS @TU_USUARIO
/.editorconfig @TU_USUARIO

# --- Documentación ---
# El README puede actualizarlo cualquier contributor.
# Si quisieras restricción, agregarías un owner específico aquí.
*.md @TU_USUARIO
* @leopansa
40 changes: 40 additions & 0 deletions .github/code_OWNERS_back.aa
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# CODEOWNERS
#
# Define quién debe revisar cambios en partes específicas del repositorio.
# GitHub solicita automáticamente la revisión de los owners cuando un PR
# modifica archivos que coinciden con algún patrón.
#
# Sintaxis: patrón @usuario-o-equipo
#
# Las reglas se evalúan de arriba hacia abajo.
# La última regla que coincida con un archivo gana.
# Los patrones siguen la misma sintaxis que .gitignore.

# --- Owner global ---
# Aplica a todo lo que no tenga una regla más específica abajo.
* @TU_USUARIO

# --- Librería principal ---
# Cualquier cambio en el código fuente requiere revisión de los maintainers.
/src/ @TU_USUARIO

# --- Tests ---
# Los tests pueden revisarlos los mismos developers, no solo maintainers.
# En un equipo real aquí irían @equipo/developers o usuarios individuales.
/tests/ @TU_USUARIO

# --- Workflows de CI/CD ---
# Los cambios en pipelines tienen un impacto alto: pueden exponer secrets,
# modificar qué checks son requeridos, o cambiar tiempos de build.
# Requieren revisión explícita de quien administra la infraestructura.
/.github/workflows/ @TU_USUARIO

# --- Configuración de seguridad y políticas ---
# CODEOWNERS mismo, editorconfig y configuración del repo.
/.github/CODEOWNERS @TU_USUARIO
/.editorconfig @TU_USUARIO

# --- Documentación ---
# El README puede actualizarlo cualquier contributor.
# Si quisieras restricción, agregarías un owner específico aquí.
*.md @TU_USUARIO
123 changes: 123 additions & 0 deletions .github/workflows/01-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Estado esperado de .github/workflows/01-ci.yml al terminar el laboratorio 04.
# Cambios respecto al 03: el trabajo se parte en dos jobs unidos con `needs`,
# y los binarios viajan de un job a otro como artefacto.
#
# Ojo con el costo: dos jobs significan dos runners, dos checkouts y dos
# restores. Partir jobs se justifica cuando ganas paralelismo o cuando
# necesitas condiciones distintas, no por estética.

name: 01 - CI

on:
push:
branches:
- main
- 'lab/**'
pull_request:
branches:
- main
workflow_dispatch:

permissions:
contents: read

env:
DOTNET_VERSION: '9.0.x'
DOTNET_NOLOGO: 'true'
DOTNET_CLI_TELEMETRY_OPTOUT: 'true'

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
name: Build
runs-on: ubuntu-latest
outputs:
version-sdk: ${{ steps.sdk.outputs.dotnet-version }}
steps:
- name: Descargar el código al runner
uses: actions/checkout@v7

- name: Instalar el SDK de .NET
id: sdk
uses: actions/setup-dotnet@v6
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

- name: Restaurar dependencias
run: dotnet restore

- name: Compilar en Release
run: dotnet build --configuration Release --no-restore

- name: Publicar los binarios compilados
uses: actions/upload-artifact@v7
with:
name: binarios
path: |
src/**/bin/Release/
tests/**/bin/Release/
retention-days: 1
if-no-files-found: error

test:
name: Test
runs-on: ubuntu-latest
needs: build
steps:
- name: Descargar el código al runner
uses: actions/checkout@v7

- name: Instalar el SDK de .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

- name: Bajar los binarios del job anterior
uses: actions/download-artifact@v7
with:
name: binarios

# El artefacto trae bin/, pero no obj/. `dotnet test --no-build` necesita
# el archivo obj/project.assets.json para resolver rutas de salida, así que
# hay que restaurar de nuevo. Es rápido porque no vuelve a compilar.
- name: Restaurar dependencias
run: dotnet restore

- name: Ejecutar pruebas
run: |
dotnet test \
--configuration Release \
--no-build \
--logger "trx;LogFileName=resultados.trx" \
--results-directory ./resultados

- name: Publicar resultados de pruebas
if: always()
uses: actions/upload-artifact@v7
with:
name: resultados-de-pruebas
path: ./resultados
retention-days: 5
if-no-files-found: error

- name: Escribir el resumen del run
if: always()
run: |
TRX=$(find ./resultados -name '*.trx' | head -1)
TOTAL=$(grep -o 'total="[0-9]*"' "$TRX" | head -1 | grep -o '[0-9]*')
PASADAS=$(grep -o 'passed="[0-9]*"' "$TRX" | head -1 | grep -o '[0-9]*')
FALLIDAS=$(grep -o 'failed="[0-9]*"' "$TRX" | head -1 | grep -o '[0-9]*')
{
echo "### Resultado de las pruebas"
echo ""
echo "| Métrica | Valor |"
echo "|---------|-------|"
echo "| Total | ${TOTAL:-0} |"
echo "| Pasadas | ${PASADAS:-0} |"
echo "| Fallidas | ${FALLIDAS:-0} |"
echo ""
echo "SDK usado en build: \`${{ needs.build.outputs.version-sdk }}\`"
} >> "$GITHUB_STEP_SUMMARY"
38 changes: 38 additions & 0 deletions .github/workflows/Test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Test

on:
push:
branches:
- main
- 'feature/**'
- 'fix/**'
pull_request:
branches:
- main

env:
DOTNET_VERSION: '9.0.x'
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true

jobs:
format:
name: Format check
runs-on: ubuntu-latest
outputs:
dotnet-version: ${{ steps.setup-dotnet.outputs.dotnet-version }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup .NET
id: setup-dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

- name: Restore dependencias
run: dotnet restore

- name: Verificar formato de código
run: dotnet format --verify-no-changes --verbosity diagnostic
19 changes: 11 additions & 8 deletions .github/workflows/matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,18 @@ jobs:

- name: Compilar
run: dotnet build --configuration Release --no-restore

- name: Ejecutar pruebas
run: |
dotnet test \
--configuration Release \
--no-restore \
--collect:"XPlat Code Coverage" \
--results-directory ./coverage \
--logger "trx;LogFileName=test-results.trx"
run: dotnet test --configuration Release --no-restore --collect:"XPlat Code Coverage" --results-directory ./coverage --logger "trx;LogFileName=test-results.trx"

#- name: Ejecutar pruebas
# run: |
# dotnet test \
# --configuration Release \
# --no-restore \
# --collect:"XPlat Code Coverage" \
# --results-directory ./coverage \
# --logger "trx;LogFileName=test-results.trx"

- name: Subir resultado por combinación
uses: actions/upload-artifact@v4
Expand Down
20 changes: 20 additions & 0 deletions src/FinancialUtils/Calculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public static class Calculator

public static int Add(int a, int b) => a + b;

public static float Add(float a, float b) => a + b;

/// <summary>
/// Resta b de a.
/// </summary>
Expand All @@ -29,7 +31,9 @@ public static class Calculator
public static decimal Divide(decimal a, decimal b)
{
if (b == 0)
{
throw new DivideByZeroException("El divisor no puede ser cero.");
}

return a / b;
}
Expand All @@ -46,13 +50,19 @@ public static decimal Divide(decimal a, decimal b)
public static decimal CompoundInterest(decimal principal, decimal rate, int periods)
{
if (principal < 0)
{
throw new ArgumentException("El capital no puede ser negativo.", nameof(principal));
}

if (rate < 0)
{
throw new ArgumentException("La tasa no puede ser negativa.", nameof(rate));
}

if (periods < 1)
{
throw new ArgumentException("Los periodos deben ser un entero positivo.", nameof(periods));
}

return principal * (decimal)Math.Pow((double)(1 + rate), periods);
}
Expand All @@ -69,16 +79,24 @@ public static decimal CompoundInterest(decimal principal, decimal rate, int peri
public static decimal LoanPayment(decimal principal, decimal annualRate, int months)
{
if (principal <= 0)
{
throw new ArgumentException("El préstamo debe ser mayor a cero.", nameof(principal));
}

if (annualRate < 0)
{
throw new ArgumentException("La tasa no puede ser negativa.", nameof(annualRate));
}

if (months < 1)
{
throw new ArgumentException("El plazo debe ser un entero positivo.", nameof(months));
}

if (annualRate == 0)
{
return Math.Round(principal / months, 2, MidpointRounding.AwayFromZero);
}

var monthlyRate = annualRate / 12;
var factor = (decimal)Math.Pow((double)(1 + monthlyRate), months);
Expand All @@ -103,7 +121,9 @@ public static decimal NetPresentValue(decimal discountRate, IEnumerable<decimal>
?? throw new ArgumentNullException(nameof(cashFlows));

if (flows.Count == 0)
{
throw new ArgumentException("Se requiere al menos un flujo de caja.", nameof(cashFlows));
}

decimal npv = 0;
for (int t = 0; t < flows.Count; t++)
Expand Down
8 changes: 8 additions & 0 deletions src/FinancialUtils/Formatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ public static class Formatter
public static string FormatCurrency(decimal amount, string currencyCode = "USD", string cultureName = "es-MX")
{
if (string.IsNullOrWhiteSpace(currencyCode))
{
throw new ArgumentException("El código de moneda no puede estar vacío.", nameof(currencyCode));
}

var culture = new CultureInfo(cultureName);
var regionInfo = new RegionInfo(cultureName);
Expand All @@ -36,7 +38,9 @@ public static string FormatCurrency(decimal amount, string currencyCode = "USD",
public static string FormatPercentage(decimal value, int decimalPlaces = 2)
{
if (decimalPlaces < 0)
{
throw new ArgumentException("Los decimales no pueden ser negativos.", nameof(decimalPlaces));
}

return $"{(value * 100).ToString($"F{decimalPlaces}")}%";
}
Expand All @@ -50,7 +54,9 @@ public static string FormatPercentage(decimal value, int decimalPlaces = 2)
public static string FormatNumber(decimal value, int decimalPlaces = 0, string cultureName = "es-MX")
{
if (decimalPlaces < 0)
{
throw new ArgumentException("Los decimales no pueden ser negativos.", nameof(decimalPlaces));
}

var culture = new CultureInfo(cultureName);
return value.ToString($"N{decimalPlaces}", culture);
Expand All @@ -64,7 +70,9 @@ public static string FormatNumber(decimal value, int decimalPlaces = 0, string c
public static decimal TruncateDecimals(decimal value, int decimalPlaces)
{
if (decimalPlaces < 0)
{
throw new ArgumentException("Los decimales no pueden ser negativos.", nameof(decimalPlaces));
}

var factor = (decimal)Math.Pow(10, decimalPlaces);
return Math.Truncate(value * factor) / factor;
Expand Down
Loading