Skip to content

Instantly share code, notes, and snippets.

@robintux
Last active November 12, 2025 12:53
Show Gist options
  • Select an option

  • Save robintux/c8eb73efd51b9d6c10d9233884843cea to your computer and use it in GitHub Desktop.

Select an option

Save robintux/c8eb73efd51b9d6c10d9233884843cea to your computer and use it in GitHub Desktop.
from __future__ import annotations
from typing import List, Optional, Union
from math import nan
import json


class Calificacion:
    """Representa una calificación con metadatos (extensible)."""
    def __init__(self, valor: float, asignatura: Optional[str] = None, fecha: Optional[str] = None):
        if not (0 <= valor <= 10):
            raise ValueError("La calificación debe estar entre 0 y 10.")
        if not isinstance(valor, (int, float)):
            raise TypeError("La calificación debe ser numérica.")
        self.valor = float(valor)
        self.asignatura = asignatura
        self.fecha = fecha

    def __repr__(self):
        return f"Calificacion(valor={self.valor}, asignatura={self.asignatura})"


class Estudiante:
    """Representa a un estudiante con nombre, edad y calificaciones estructuradas."""

    def __init__(self, nombre: str, edad: int):
        if not isinstance(nombre, str) or not nombre.strip():
            raise ValueError("El nombre debe ser una cadena no vacía.")
        if not isinstance(edad, int) or edad < 0:
            raise ValueError("La edad debe ser un entero no negativo.")
        
        self.nombre = nombre.strip()
        self.edad = edad
        self._calificaciones: List[Calificacion] = []

    def agregar_calificacion(
        self, 
        valor: Union[int, float], 
        asignatura: Optional[str] = None, 
        fecha: Optional[str] = None
    ) -> None:
        """Agrega una calificación con metadatos opcionales."""
        calif = Calificacion(valor, asignatura, fecha)
        self._calificaciones.append(calif)

    def agregar_calificaciones(self, notas: List[Union[int, float]]) -> None:
        """Agrega múltiples calificaciones sin metadatos (modo rápido)."""
        for nota in notas:
            self.agregar_calificacion(nota)

    @property
    def promedio(self) -> float:
        """Devuelve el promedio aritmético. Si no hay calificaciones, devuelve NaN."""
        if not self._calificaciones:
            return nan
        return sum(c.valor for c in self._calificaciones) / len(self._calificaciones)

    @property
    def calificaciones_valores(self) -> List[float]:
        """Devuelve solo los valores numéricos de las calificaciones."""
        return [c.valor for c in self._calificaciones]

    def estadisticas_descriptivas(self) -> dict:
        """Devuelve estadísticas básicas: promedio, mediana, desviación estándar."""
        from statistics import mean, median, stdev
        valores = self.calificaciones_valores
        if not valores:
            return {"promedio": nan, "mediana": nan, "desviacion_estandar": nan}
        return {
            "promedio": mean(valores),
            "mediana": median(valores),
            "desviacion_estandar": stdev(valores) if len(valores) > 1 else 0.0
        }

    def to_dict(self) -> dict:
        """Serializa el estudiante a un diccionario (compatible con JSON)."""
        return {
            "nombre": self.nombre,
            "edad": self.edad,
            "calificaciones": [
                {
                    "valor": c.valor,
                    "asignatura": c.asignatura,
                    "fecha": c.fecha
                }
                for c in self._calificaciones
            ],
            "promedio": self.promedio  # nan no es JSON serializable; manejar en capa superior
        }

    def __repr__(self):
        return f"Estudiante(nombre='{self.nombre}', edad={self.edad}, n_calif={len(self._calificaciones)})"

    def __str__(self):
        return f"{self.nombre} ({self.edad} años) - Promedio: {self.promedio:.2f} (n={len(self._calificaciones)})"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment