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
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"
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# ⚙️ Workshop: GitHub Intermedio

Hola

## Pipelines, Políticas y Productividad con .NET 9

![GitHub Actions](https://img.shields.io/badge/GitHub%20Actions-Workflows-2088FF?logo=githubactions&logoColor=white)
Expand Down
38 changes: 38 additions & 0 deletions scripts/verificar-entorno.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Verificación previa al workshop. Ejecuta esto ANTES de la sesión.
# Si algo falla aquí, resuélvelo antes y no en los primeros diez minutos
# del laboratorio.

set -u
fallas=0

check() {
local nombre="$1"; shift
if "$@" > /dev/null 2>&1; then
printf ' OK %s\n' "$nombre"
else
printf ' FALLA %s\n' "$nombre"
fallas=$((fallas + 1))
fi
}

echo "Verificando herramientas"
check "git instalado" git --version
check "dotnet instalado" dotnet --version
check "gh (GitHub CLI) instalado" gh --version
check "gh autenticado" gh auth status

echo
echo "Verificando el proyecto"
check "restore" dotnet restore
check "build" dotnet build --configuration Release --no-restore
check "test" dotnet test --configuration Release --no-build

echo
if [ "$fallas" -eq 0 ]; then
echo "Todo listo. Puedes empezar el laboratorio 00."
exit 0
fi

echo "$fallas verificación(es) fallaron. Revisa laboratorio/00-preparacion.md."
exit 1
20 changes: 20 additions & 0 deletions src/FinancialUtils/Calculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public static class Calculator
/// </summary>
public static decimal Add(decimal a, decimal b) => a + b;

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

/// <summary>
/// Resta b de a.
/// </summary>
Expand All @@ -27,7 +29,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 @@ -44,13 +48,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 @@ -67,16 +77,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 @@ -101,7 +119,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
4 changes: 2 additions & 2 deletions tests/FinancialUtils.Tests/CalculatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,10 @@ public void LoanPayment_InvalidMonths_ThrowsArgumentException(int months)
public void NetPresentValue_CalculatesCorrectly()
{
// Inversión inicial -1000, flujos futuros de 400 por 3 periodos a tasa 10%
// NPV = -1000 + 400/1.1 + 400/1.21 + 400/1.331 ≈ -0.64
// NPV = -1000 + 400/1.1 + 400/1.21 + 400/1.331 ≈ -5.26
var cashFlows = new[] { -1000m, 400m, 400m, 400m };
var result = Calculator.NetPresentValue(0.10m, cashFlows);
Assert.Equal(-0.64m, result);
Assert.Equal(-5.26m, result);
}

[Fact]
Expand Down
2 changes: 1 addition & 1 deletion tests/FinancialUtils.Tests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
global using Xunit;
global using Xunit;