IDS Guía de estudio

Semana 10 · Módulo 9

Transición a C#: tipado estático y plataforma .NET

Todo lo que aprendiste sigue valiendo: variables, decisiones, bucles, funciones. Lo que cambia es que en C# declaras qué tipo tiene cada dato y un compilador revisa el programa completo antes de ejecutarlo.

Pregunta orientadora¿Qué cambia y qué se mantiene cuando paso un programa de Python a un lenguaje compilado y fuertemente tipado?

Lecturas
TRO 1 · TRO 3 · MSDN
Proyecto
Fase 6 (esta semana)
Evaluación
Control 4: próxima semana

Cómo usar esta guía

Antes de claseTRO 1 y 3, y la guía rápida de Microsoft Learn. Instala el SDK de .NET (ver entorno).
Durante el laboratorioTaller de portabilidad: traducir tus soluciones de las semanas 2 a 4. Usa la tabla de traducción.
Después de claseLos errores de compilación más comunes están en esta tabla. Practica leyéndolos.
ProyectoFase 6: README definitivo y bitácora de IA (el proyecto sigue en Python).
  • Pregunta
  • →
  • Exploración
  • →
  • Implementación
  • →
  • Resultado
  • →
  • Interpretación
  • →
  • Verificación
Sobre ejecutar el código

Los bloques de C# no se ejecutan en esta página: necesitan el compilador de .NET. Cópialos a tu proyecto y usa dotnet run. Todas las salidas que ves aquí salieron de ejecutarlos de verdad con .NET 8.

Qué puedes usar esta semana

Sí, desde ahora

  • La lógica de las semanas 1 a 9: lo que cambia es la sintaxis
  • Tipos int, double, decimal, string, bool, char; var y const
  • Console.WriteLine, Console.ReadLine, interpolación $"..."
  • int.Parse, decimal.TryParse, Math.Round
  • if / else if / else, switch, while, for
  • Métodos static con tipos de parámetros y de retorno

Todavía no / fuera del curso

  • Clases, propiedades, List<T> y herencia en C# → semana 11
  • LINQ, async, interfaces gráficas: materias posteriores

El proyecto integrador no se programa en C#. Estas semanas son el puente hacia Programación Orientada a Objetos y Estructuras de Datos.

Lecturas de preparación

Reading checkpoint

¿Qué hace el compilador de C# antes de que el programa se ejecute?

Si hay un error de tipos, el programa ni siquiera se genera. En Python ese error aparecería al ejecutar la línea.

¿Qué es el CLR?

CLR = Common Language Runtime. El JIT (just-in-time) traduce el IL a código de máquina mientras el programa corre.

¿Qué tipo conviene para dinero en C#?

decimal representa exactamente los decimales en base 10: 0.1m + 0.2m == 0.3m es verdadero. Los literales llevan m: 12.50m.

¿Qué produce 7 / 2 en C#?

A diferencia de Python 3, en C# int / int es división entera. Para 3.5: 7 / 2.0 o (double)7 / 2.

¿Qué pasa con int cantidad = "4";?

El compilador lo rechaza (error CS0029). Para convertir texto: int.Parse("4").

¿Qué hace decimal.TryParse(texto, out decimal monto)?

Es el equivalente cómodo de try: float(texto) except ValueError de la semana 7.

¿Qué le falta a esta línea?

Console.WriteLine("Total")

En C# cada instrucción termina en ;. El compilador lo reporta como error CS1002.

¿Qué define los bloques (cuerpo de un if, de un bucle) en C#?

La sangría en C# es solo para leer mejor; lo que manda son las llaves. Aun así, indenta igual que en Python.

Compilado vs. interpretado, y tu primer programa

En palabras simples

Python es un intérprete simultáneo: traduce mientras hablas; si una frase no tiene sentido, te enteras en ese momento. C# es un traductor de libros: revisa y traduce el libro completo antes de publicarlo; si una frase no cuadra, el libro no sale.

Python interpreta el archivo y los errores de tipo aparecen al ejecutar; C# compila a lenguaje intermedio IL que el CLR convierte a código de máquina con JIT, y los errores de tipo se detectan antes de ejecutar.
En C#, los errores de tipo se detectan al compilar, antes de que el programa llegue a un usuario.

Crear y ejecutar un proyecto

dotnet --version                 # comprobar la instalación del SDK
dotnet new console -o LaCeiba    # crea la carpeta y Program.cs
cd LaCeiba
dotnet run                       # compila y ejecuta

Caso: la factura de la semana 2, en C#

Pregunta

¿Qué cambia al escribir el mismo cálculo en C#?

Implementación (estilo moderno)
// Program.cs (.NET 6 o superior: instrucciones de nivel superior)
decimal precioUnitario = 12.50m;
int cantidad = 4;
const decimal TasaIva = 0.13m;

decimal subtotal = precioUnitario * cantidad;
decimal total = subtotal * (1 + TasaIva);
Console.WriteLine($"Subtotal: {subtotal:F2}");
Console.WriteLine($"Total:    {total:F2}");
Subtotal: 50.00
Total:    56.50
Implementación (estilo clásico, el que verás en muchos libros)
using System;

namespace LaCeiba
{
    class Program
    {
        static void Main(string[] args)
        {
            decimal precioUnitario = 12.50m;
            int cantidad = 4;
            Console.WriteLine($"Subtotal: {precioUnitario * cantidad:F2}");
        }
    }
}
Subtotal: 50.00

Ambos son el mismo programa. Desde .NET 6 el compilador genera por ti la clase y el static void Main. En TRO y en exámenes verás la forma clásica: identifica namespace (agrupa clases), class Program y static void Main (punto de entrada).

Interpretación

Cada variable declara su tipo (decimal, int). 12.50m es un literal decimal. {total:F2} formatea con 2 decimales. const es una constante de verdad: el compilador impide cambiarla.

Verificación

12.50 × 4 = 50 y 50 × 1.13 = 56.50, igual que en la semana 2 ✔. Prueba quitar la m de 12.50m: el compilador protesta porque 12.50 es double y no se convierte solo a decimal (CS0664).

Tipos, variables y operadores

En palabras simples

En Python una variable es una etiqueta que se puede pegar en cualquier caja. En C# cada variable es un casillero con forma: el de int solo acepta enteros, el de string solo texto. Intentar meter otra cosa no compila.

int cajas = 17;
int porCamion = 5;
Console.WriteLine(cajas / porCamion);          // división entera
Console.WriteLine(cajas % porCamion);
Console.WriteLine(cajas / (double)porCamion);  // conversión explícita (cast)

double d = 0.1 + 0.2;
decimal m = 0.1m + 0.2m;
Console.WriteLine(d == 0.3);
Console.WriteLine(m == 0.3m);

var nombre = "Aceite";         // var: el compilador deduce string
char inicial = nombre[0];
bool hayStock = true;
Console.WriteLine($"{nombre} empieza con {inicial}; ¿stock? {hayStock}");

decimal mitad = 2.665m;
Console.WriteLine(Math.Round(mitad, 2));                                   // redondeo bancario
Console.WriteLine(Math.Round(mitad, 2, MidpointRounding.AwayFromZero));   // redondeo comercial
3
2
3.4
False
True
Aceite empieza con A; ¿stock? True
2.66
2.67
PythonC#Úsalo para
intint (long si es enorme)cantidades, contadores
floatdoublemedidas, cálculos científicos
float con dinerodecimal (literal 12.50m)dinero: siempre
strstring (y char para un carácter: 'A')texto
bool (True)bool (true, en minúscula)sí/no
Dos trampas nuevas
  • int / int trunca: 17 / 5 es 3. Si necesitas decimales, convierte uno de los dos.
  • Math.Round redondea “al par” por defecto (2.675 → 2.68, pero 2.665 → 2.66). Para dinero usa MidpointRounding.AwayFromZero.

Traducir de Python a C#

En palabras simples

Traducir no es cambiar palabras una por una: la lógica es la misma (tus algoritmos de las semanas 1 a 9 siguen valiendo). Cambian la gramática: tipos, llaves, punto y coma y nombres de funciones.

Tabla de traducción de Python a C#: declaración de variables con tipo, conversión con int.Parse, if y else if con llaves, for con contador, while, Console.WriteLine y métodos static.
Las equivalencias que más vas a usar en el taller de portabilidad.

Caso: la caja registradora de la semana 3

Pregunta

¿Cómo se ve el bucle con centinela y validación de la semana 3 en C#?

Exploración

input() → Console.ReadLine() (puede devolver null, por eso el tipo es string?). La validación con try o isdigit → decimal.TryParse. += 1 → ++.

Implementación
decimal total = 0;
int ventas = 0;

Console.WriteLine("Montos de venta (escriba 'fin' para cerrar):");
while (true)
{
    Console.Write("Monto: ");
    string? entrada = Console.ReadLine();
    if (entrada == null || entrada.Trim().ToLower() == "fin")
    {
        break;
    }

    if (decimal.TryParse(entrada, out decimal monto) && monto > 0)
    {
        total += monto;
        ventas++;
    }
    else
    {
        Console.WriteLine($"  '{entrada}' no es un monto válido.");
    }
}

Console.WriteLine($"Ventas: {ventas}  Total: {total:F2}");
if (ventas > 0)
{
    Console.WriteLine($"Promedio: {total / ventas:F2}");
}
Montos de venta (escriba 'fin' para cerrar):
Monto: 25.50
Monto: 100
Monto: diez
  'diez' no es un monto válido.
Monto: 74.50
Monto: fin
Ventas: 3  Total: 200.00
Promedio: 66.67
Interpretación

TryParse junta dos pasos: “¿es número?” y “conviértelo”. out decimal monto declara la variable donde queda el resultado. && es and, || es or, ! es not.

Verificación

Mismo resultado que en Python: 3 ventas, $200.00, promedio $66.67 ✔. La salida del bloque es la de una ejecución real con esas entradas.

Funciones → métodos static

La tarifa de envío de las semanas 2 y 4, con tipo en cada parámetro y en el retorno:

Console.WriteLine(TarifaEnvio(1.01, "rural"));
Console.WriteLine(TarifaEnvio(20));
Console.WriteLine(TarifaEnvio(25));

static decimal TarifaEnvio(double pesoKg, string zona = "urbana")
{
    if (pesoKg <= 0 || pesoKg > 20)
    {
        return -1;                      // señal de "no se puede enviar"
    }

    decimal tarifa;
    if (pesoKg <= 1)
    {
        tarifa = 3.50m;
    }
    else if (pesoKg <= 5)
    {
        tarifa = 6.00m;
    }
    else
    {
        tarifa = 12.00m;
    }

    if (zona == "rural")
    {
        tarifa += 2.50m;
    }
    return tarifa;
}
8.50
12.00
-1

El parámetro opcional funciona igual que en Python (zona = "urbana"). Como decimal no puede ser null, aquí se usa −1 como señal; en la semana 11 verás una alternativa mejor (lanzar una excepción).

switch y for

string metodo = "tarjeta";
decimal monto = 80m;

decimal comision = metodo switch
{
    "efectivo" => 0m,
    "tarjeta" => monto * 0.035m,
    "transferencia" => monto < 100 ? 0.75m : 0m,
    _ => throw new ArgumentException($"método desconocido: {metodo}")
};
Console.WriteLine($"Comisión: {comision:F2}");

for (int mes = 1; mes <= 3; mes++)
{
    Console.WriteLine($"Mes {mes}: saldo {1000m * (decimal)Math.Pow(1.02, mes):F2}");
}
Comisión: 2.80
Mes 1: saldo 1020.00
Mes 2: saldo 1040.40
Mes 3: saldo 1061.21

El switch con => reemplaza cadenas largas de else if sobre un mismo valor; _ es “cualquier otro caso”. for (int mes = 1; mes <= 3; mes++) equivale a for mes in range(1, 4): inicio; condición; paso.

Errores de compilación frecuentes

En palabras simples

Un error de compilación es el corrector ortográfico del libro: te marca la línea exacta y el tipo de error antes de publicar. Aprender a leerlo ahorra horas.

Así se ve uno de verdad (compilado con .NET 8):

int cantidad = "4";
Console.WriteLine(cantidad);
Program.cs(1,16): error CS0029: Cannot implicitly convert type 'string' to 'int'

Se lee así: archivo Program.cs, línea 1, columna 16, código CS0029, y el mensaje (“no se puede convertir implícitamente string a int”). Busca el código en Microsoft Learn si no lo entiendes.

CódigoQué significaEjemploArreglo
CS1002falta ;int x = 5int x = 5;
CS0103el nombre no existe (mal escrito o fuera de su bloque)Console.WriteLine(totl);corregir el nombre o declarar antes
CS0029tipos incompatiblesint c = "4";int c = int.Parse("4");
CS0165variable usada sin valor asignadodecimal t; if (x) t = 1; Console.Write(t);asignar en todas las ramas o al declarar
CS0664literal double donde va decimaldecimal p = 12.5;decimal p = 12.5m;
CS0161no todos los caminos devuelven valormétodo con return solo dentro de un ifagregar un return final
Por qué esto importa

CS0165 y CS0161 son los errores de las semanas 2 y 4 (NameError por variable sin asignar en una rama, None inesperado): en Python aparecían con el programa corriendo; en C# el compilador los detecta antes. Es la ventaja del tipado estático.

Tabla de traza

¿Qué imprime cada expresión en C#?

Escribe el valor como lo mostraría Console.WriteLine (los booleanos se muestran True/False).

Expresión C#Resultado
7 / 2
7 / 2.0
7 % 3
"5" + 3
int.Parse("12") + 1
10m / 4
5 == 5.0
!(3 > 2 && 2 > 5)
¿Por qué "5" + 3 da “53” y no un error?

Si uno de los operandos de + es string, C# convierte el otro a texto y concatena. En Python "5" + 3 es TypeError. Mismo símbolo, reglas distintas: por eso se estudia la transición.

Ejercicios

E1 · La factura con descuento, en C#

traducción · ★★

Traduce calcular_total de la semana 4 (descuento del 10 % desde $50 e IVA del 13 %) a un método static. Comprueba los mismos tres casos de la traza de la semana 1.

Solución
const decimal TasaIva = 0.13m;
const decimal TasaDescuento = 0.10m;
const decimal MinimoDescuento = 50m;

Console.WriteLine(CalcularTotal(12.50m, 4));
Console.WriteLine(CalcularTotal(8.00m, 3));
Console.WriteLine(CalcularTotal(30.00m, 2));

static decimal CalcularTotal(decimal precioUnitario, int cantidad)
{
    decimal subtotal = precioUnitario * cantidad;
    decimal descuento = subtotal >= MinimoDescuento ? subtotal * TasaDescuento : 0m;
    decimal total = (subtotal - descuento) * (1 + TasaIva);
    return Math.Round(total, 2, MidpointRounding.AwayFromZero);
}
50.85
27.12
61.02

condicion ? a : b es el operador ternario (la expresión condicional de Python). Resultados idénticos a Python: 50.85, 27.12 y 61.02 ✔.

E2 · Tabla de interés compuesto

for + formato · ★

Traduce la tabla de ahorro a plazo de la semana 3 ($1,000 al 8 % anual, 5 años) con for y alineación de columnas.

Solución
decimal capital = 1000m;
const decimal Tasa = 0.08m;

decimal saldo = capital;
Console.WriteLine("Año   Interés      Saldo");
for (int anio = 1; anio <= 5; anio++)
{
    decimal interes = saldo * Tasa;
    saldo += interes;
    Console.WriteLine($"{anio,3}  {interes,8:F2}  {saldo,9:F2}");
}
Console.WriteLine($"Ganancia total: ${saldo - capital:N2}");
Año   Interés      Saldo
  1     80.00    1080.00
  2     86.40    1166.40
  3     93.31    1259.71
  4    100.78    1360.49
  5    108.84    1469.33
Ganancia total: $469.33

{valor,8:F2} alinea a la derecha en 8 caracteres con 2 decimales (como :>8.2f en Python). N2 agrega separador de miles. Como aquí se usa decimal, no hay error de redondeo acumulado.

E3 · Validador de claves

foreach + char · ★★

Traduce el validador de la semana 2: mínimo 8 caracteres, solo letras y dígitos, y al menos una letra y un dígito. Esta vez recorre la clave carácter por carácter.

Solución
Console.WriteLine(ValidarClave("ceiba2026"));
Console.WriteLine(ValidarClave("ceibaceiba"));
Console.WriteLine(ValidarClave("12345678"));
Console.WriteLine(ValidarClave("Ceiba26"));

static string ValidarClave(string clave)
{
    if (clave.Length < 8)
    {
        return "Rechazada: mínimo 8 caracteres";
    }
    bool tieneLetra = false;
    bool tieneDigito = false;
    foreach (char c in clave)
    {
        if (char.IsLetter(c)) tieneLetra = true;
        else if (char.IsDigit(c)) tieneDigito = true;
        else return "Rechazada: solo letras y dígitos";
    }
    if (!tieneLetra) return "Rechazada: falta una letra";
    if (!tieneDigito) return "Rechazada: falta un dígito";
    return "Aceptada";
}
Aceptada
Rechazada: falta un dígito
Rechazada: falta una letra
Rechazada: mínimo 8 caracteres

foreach (char c in clave) recorre un string como for c in clave. char.IsLetter y char.IsDigit equivalen a isalpha e isdigit.

E4 · Auditoría: el programa que no compila

errores de compilación · ★★

Este código tiene cuatro errores de compilación. Para cada uno, di el código CS y la corrección.

int ventas = 3
decimal total = 250.5;
if (ventas > 0) {
    promedio = total / ventas;
}
Console.WriteLine("Promedio: " + promedio);
Solución
  1. CS1002: falta ; en la línea 1.
  2. CS0664: 250.5 es double; para decimal va 250.5m.
  3. CS0103: promedio no está declarada.
  4. CS0165: aunque se declare, si ventas fuera 0 no tendría valor al imprimirla.
int ventas = 3;                       // CS1002: faltaba ;
decimal total = 250.5m;               // CS0664: literal decimal con m
decimal promedio = 0;                 // CS0103: no estaba declarada
                                      // CS0165: y necesita valor en todos los caminos
if (ventas > 0)
{
    promedio = total / ventas;
}
Console.WriteLine($"Promedio: {promedio:F2}");
Promedio: 83.50

Práctica intensiva

Ejercicios rápidos sobre las diferencias de C# con Python y problemas de traducción tipo examen.

Ejercicios rápidos · ¿Qué imprime en C#?

Escribe lo que muestra Console.WriteLine. Si no compila, escribe error.

CódigoTu respuesta
Console.WriteLine(9 / 4);
Console.WriteLine(9 / 4.0);
Console.WriteLine(9 % 4);
Console.WriteLine("Total: " + 5 + 5);
Console.WriteLine(5 + 5 + " unidades");
int x = 3;
x++;
x += 2;
Console.WriteLine(x);
int n = "7";
for (int i = 0; i < 3; i++)
    Console.Write(i);
Console.WriteLine(10 > 3 && 2 > 5);

Problemas tipo examen

P1 · Movimientos de caja con validación

foreach + TryParse + continue · ★★★

Procesa movimientos de texto ("+50", "-20"…) sobre un saldo que empieza en 0. Rechaza los que no son números y las salidas que dejarían el saldo negativo, informando el motivo.

Tu programa debe cumplir estos casos de prueba:

DatoEsperado
Movimientos+50, −20, abc, −100, +15, −30
Saldo final15.00
Rechazos2 (abc y −100)
Pista

decimal.TryParse("+50", out ...) acepta el signo. continue funciona igual que en Python.

Solución
string[] movimientos = { "+50", "-20", "abc", "-100", "+15", "-30" };
decimal saldo = 0m;
int rechazos = 0;

foreach (string mov in movimientos)
{
    if (!decimal.TryParse(mov, out decimal monto))
    {
        Console.WriteLine($"  '{mov}': formato inválido");
        rechazos++;
        continue;
    }
    if (saldo + monto < 0)
    {
        Console.WriteLine($"  {mov}: rechazado, saldo insuficiente ({saldo:F2})");
        rechazos++;
        continue;
    }
    saldo += monto;
}
Console.WriteLine($"Saldo final: {saldo:F2}  Rechazos: {rechazos}");
  'abc': formato inválido
  -100: rechazado, saldo insuficiente (30.00)
Saldo final: 15.00  Rechazos: 2

P2 · Cuota y meta de ahorro como métodos

métodos static + Math · ★★★

Traduce calcular_cuota (semana 4) y el “¿cuándo se duplica mi inversión?” (semana 3) a métodos static. La potencia se calcula con Math.Pow, que trabaja con double: convierte con cuidado.

Tu programa debe cumplir estos casos de prueba:

LlamadaEsperado
Cuota(1200m, 0.18m, 6)210.63
Cuota(1200m, 0m, 12)100.00
Duplicar al 8 % anual (mensual)105 meses
Pista

(double)monto convierte de decimal a double; (decimal)cuota vuelve. Estas conversiones explícitas (casts) son obligatorias en C#.

Solución
Console.WriteLine($"Cuota: {Cuota(1200m, 0.18m, 6):F2}");
Console.WriteLine($"Cuota sin interés: {Cuota(1200m, 0m, 12):F2}");
Console.WriteLine($"Meses para duplicar al 8 %: {MesesParaMeta(1000m, 0.08m / 12, 2000m)}");

static decimal Cuota(decimal monto, decimal tasaAnual, int meses)
{
    if (tasaAnual == 0)
    {
        return Math.Round(monto / meses, 2, MidpointRounding.AwayFromZero);
    }
    double i = (double)tasaAnual / 12;
    double cuota = (double)monto * i / (1 - Math.Pow(1 + i, -meses));
    return Math.Round((decimal)cuota, 2, MidpointRounding.AwayFromZero);
}

static int MesesParaMeta(decimal saldo, decimal tasaMensual, decimal meta)
{
    int meses = 0;
    while (saldo < meta)
    {
        saldo *= 1 + tasaMensual;
        meses++;
    }
    return meses;
}
Cuota: 210.63
Cuota sin interés: 100.00
Meses para duplicar al 8 %: 105

Verificación: la cuota coincide con la de Python en las semanas 3 y 8 ✔. 105 meses son 8.75 años: con capitalización mensual se duplica antes que con la anual (10 años).

Proyecto integrador · Fase 6

Semana 10 · Documentación técnica, auditoría de IA y métricas. El proyecto sigue en Python.

Entregables de esta fase

Plantilla sugerida para la bitácora:

# Bitácora de transparencia técnica (uso de IA)

| Fecha | Integrante | Herramienta | Qué se pidió | Qué se aceptó | Qué se corrigió y cómo se verificó |
|---|---|---|---|---|---|
| 2026-10-02 | Ana | Asistente X | Estructura inicial de la clase Factura | El esqueleto de métodos | Tenía el saldo como atributo de clase (compartido); se movió a __init__ y se agregó la prueba test_facturas_independientes |
| 2026-10-05 | Luis | Asistente X | Lectura de CSV | csv.DictReader | No manejaba montos vacíos; se agregó try/except ValueError y el caso CB-04 de la matriz |

## Criterios de auditoría usados
1. ¿Ejecuta? 2. ¿Hace lo que se pidió? 3. ¿Usa las variables correctas?
4. ¿El resultado tiene sentido? 5. ¿Cómo se comprueba? 6. ¿Qué supuestos introduce?

Preguntas de defensa

¿Qué partes usaron IA y cómo saben que están bien?

Señala filas concretas de la bitácora y la prueba que respalda cada corrección. “Funcionó cuando lo probé” no es suficiente.

Si instalo su proyecto desde cero, ¿qué hago?

El README debe permitirlo sin preguntarles nada: requisitos, git clone, cómo ejecutar, cómo correr pruebas.

¿Qué supuestos de negocio tomaron?

Las decisiones de frontera (≥ vs >), el orden de descuentos, qué pasa con datos vacíos: todo documentado.

Rumbo al Control 4 y al Final

El Examen Final evalúa lógica en Python y fundamentos formales de C#. De esta semana:

Fuentes