Convertir registros de trabajo en una tabla y gráfico de resumen mensual
Valida las fechas y las horas de trabajo de un CSV y después guarda los recuentos mensuales de registros y las horas totales como un CSV y un gráfico de barras.
Contenido revisado 2026.09.19Incluye archivos de ejemplo
Ver el índice
La traducción se ha realizado con IA. Comprueba el código, las unidades y los valores junto con el original. La revisión por hablantes nativos de cada idioma aún no se ha completado. English
Para quién esPersonal de oficina que quiere gestionar resúmenes mensuales recurrentes de trabajo con código Python sencillo
Preparación
Ten preparados Python 3.12 y una terminal. Comprueba la versión con python --version.
Extrae por completo el ZIP descargado y abre una terminal en la carpeta que contiene example.py.
Empieza con los datos de práctica incluidos y después compara los resultados con los archivos originales.
Este ejemplo utiliza work_log.csv y Matplotlib. El CSV debe usar encoding UTF-8 o UTF-8 BOM.
01Empezar definiendo qué significa una fila
Trata cada fila como un registro de trabajo y suma hours al mes que contiene su date. record_count es el número de filas, no el número de personas ni de días laborables. hours utiliza horas decimales. 1.50 significa 1 hora y 30 minutos, no 1 hora y 50 minutos.
Los datos incluidos contienen 9 registros de trabajo ficticios de enero a marzo de 2026. Se trata de un ejercicio de suma de números existentes por mes, no de una herramienta para medir el tiempo de trabajo ni evaluar el rendimiento.
02Comprobar las columnas y valores de entrada
Columna
Formato
Regla de validación
record_id
Texto como R001
Debe ser único dentro del archivo
date
2026-01-06
YYYY-MM-DD; una fecha que exista realmente
task
자료 정리
No debe estar vacío
hours
2.50
Un número finito de 0 o más, con hasta dos decimales
No añadas title rows, total rows ni números con comas. Si el script encuentra un duplicate ID, se detiene para evitar doble conteo. Los records con IDs diferentes se cuentan por separado aunque sus task descriptions coincidan.
03Preparar los archivos y ejecutar el ejemplo
Extrae el ZIP y comprueba que el input file indicado en README.txt esté en la misma carpeta que example.py. No ejecutes el ejemplo desde dentro del ZIP archive.
Ejecuta los commands siguientes, una línea cada vez, en la carpeta que contiene example.py. En Windows, si python no está disponible pero sí py, sustituye python en cada command por py -3.12.
Cuando aparezca el completion message, abre la nueva carpeta outputs. Si outputs ya existe, cámbiale primero el nombre para conservar los resultados anteriores y después vuelve a ejecutar el ejemplo.
La instalación requiere conexión a internet. BASE en el código establece los paths de input y output relativos a example.py, no a la terminal folder actual.
04Código completo y método de agregación
El código valida las fechas con date.fromisoformat y crea month keys con formato YYYY-MM. Suma hours con Decimal y los escribe en la tabla con dos decimales. Los values se convierten a ordinary floating-point numbers únicamente para plotting. Agg mode guarda un PNG en lugar de abrir una chart window.
example.py
"""Summarize fictional work-log records by calendar month."""
import csv
import math
from collections import defaultdict
from datetime import date
from decimal import Decimal, InvalidOperation
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
BASE = Path(__file__).resolve().parent
HEADERS = ["record_id", "date", "task", "hours"]
def main():
destination = BASE / "outputs"
if destination.exists():
raise ValueError("outputs already exists; rename it before running again.")
totals = defaultdict(lambda: {"records": 0, "hours": Decimal("0")})
seen = set()
with (BASE / "work_log.csv").open(encoding="utf-8-sig", newline="") as file:
reader = csv.DictReader(file)
if reader.fieldnames != HEADERS:
raise ValueError(f"Expected headers: {HEADERS}")
for line, row in enumerate(reader, start=2):
if None in row or any(value is None or not value.strip() for value in row.values()):
raise ValueError(f"Row {line}: missing or extra field.")
if row["record_id"] in seen:
raise ValueError(f"Row {line}: duplicate record_id.")
seen.add(row["record_id"])
parsed_date = date.fromisoformat(row["date"])
if parsed_date.isoformat() != row["date"]:
raise ValueError(f"Row {line}: use YYYY-MM-DD dates.")
try:
hours = Decimal(row["hours"])
except InvalidOperation as error:
raise ValueError(f"Row {line}: hours must be a number.") from error
if not hours.is_finite() or hours < 0 or hours.as_tuple().exponent < -2:
raise ValueError(f"Row {line}: hours must be finite, nonnegative, with at most 2 decimals.")
month = parsed_date.strftime("%Y-%m")
totals[month]["records"] += 1
totals[month]["hours"] += hours
if not totals:
raise ValueError("No work records were found.")
months = sorted(totals)
figure, axis = plt.subplots(figsize=(8, 4.6), layout="constrained")
values = [float(totals[month]["hours"]) for month in months]
if not all(math.isfinite(value) and math.isfinite(value * 1.2) for value in values):
plt.close(figure)
raise ValueError("Monthly total is too large to plot as a finite number.")
bars = axis.bar(months, values, color="#2B6578", width=0.55)
axis.bar_label(bars, labels=[f"{value:.2f}" for value in values], padding=4)
axis.set(title="Monthly work hours | Fictional data", xlabel="Calendar month", ylabel="Hours")
axis.set_ylim(0, max(values) * 1.2 if max(values) > 0 else 1)
axis.spines[["top", "right"]].set_visible(False)
axis.set_axisbelow(True)
axis.grid(axis="y", alpha=0.2)
destination.mkdir(exist_ok=False)
with (destination / "monthly_summary.csv").open("x", encoding="utf-8-sig", newline="") as file:
writer = csv.writer(file)
writer.writerow(["month", "record_count", "total_hours"])
for month in months:
writer.writerow([month, totals[month]["records"], f"{totals[month]['hours']:.2f}"])
figure.savefig(destination / "monthly_hours.png", dpi=160)
plt.close(figure)
total_hours = sum((values["hours"] for values in totals.values()), Decimal("0"))
print(f"Records: {len(seen)}; months: {len(months)}; hours: {total_hours:.2f}")
print("Created outputs/monthly_summary.csv and outputs/monthly_hours.png.")
if __name__ == "__main__":
try:
main()
except (OSError, ValueError, csv.Error) as error:
raise SystemExit(f"Stopped: {error}") from error
05Leer la tabla de resumen y el gráfico
El script crea outputs/monthly_summary.csv y outputs/monthly_hours.png. El chart muestra únicamente los months presentes en el input, ordenados por year y month. Los axis labels en inglés permiten ejecutar el ejemplo sin instalar una Korean font separada.
month
record_count
total_hours
2026-01
3
6.50
2026-02
3
7.50
2026-03
3
9.50
Total general para comprobación
9
23.50
Monthly hours chart for fictional work records. January: 6.5 hours; February: 7.5 hours; March: 9.5 hours.
06Comprobar los totales y los límites entre meses
En el summary CSV, comprueba que enero sume 2.50 + 1.00 + 3.00 = 6.50 hours.
Comprueba que la suma de los valores mensuales de record_count coincida con los 9 input records.
Comprueba que total_hours sume 23.50 y compara el valor de cada mes con el número de su chart bar.
Copia la practice folder, cambia la date del último record a 2026-04-01 y vuelve a ejecutar. Comprueba que las hours se dividan en 5.50 para marzo y 4.00 para abril.
07Cuando una fecha o un número detiene el script
Problema
Cómo corregirlo
duplicate record_id
Comprueba si el mismo record se introdujo dos veces.
day is out of range / Invalid isoformat
Comprueba si hay fechas imposibles como 2026-02-30 o un date format incorrecto.
hours must be a number
Introduce únicamente un número como 2.5 para hours. Elimina unit labels y thousands separators.
hours must be finite…
Comprueba si hay valores negativos, NaN, Infinity o más de dos decimales.
Korean text appears garbled
Vuelve a guardar el CSV original en UTF-8.
outputs already exists
Conserva los resultados anteriores y después cambia el nombre de la result folder.
08Conocer los límites antes de utilizar los resultados en un informe
Los records que contienen solo dates se agrupan por calendar month. No se gestionan start and end times, night shifts, time zones ni tasks que crucen medianoche.
Los months sin input no se añaden como filas con 0 hours. Los missing records deben distinguirse de 0 hours reales.
Solo se bloquean duplicate IDs. Comprueba en los datos originales si la misma task se introdujo dos veces con IDs distintos.
El summary CSV y el PNG reflejan los datos en el momento de la ejecución. Después de editar el CSV, vuelve a ejecutar el script para obtener nuevos resultados.
Registro de ejecución y verificación
Windows 11 (10.0.26200), CPython 3.12.14 (64-bit). openpyxl 3.1.5, Matplotlib 3.10.8. Las bibliotecas utilizadas por cada ejemplo están fijadas en requirements.txt.
Se confirmaron 3 filas mensuales, 23.50 hours y 9 records
Se verificaron la creación del PNG y los resultados después de cambiar un record a través de un month boundary
Se comprobaron fechas no válidas, duplicate IDs, negative/NaN hours y header errors
Se confirmaron los hashes originales sin cambios y la conservación de los resultados existentes
Límites de la verificación
La correspondencia con las aggregation rules de sistemas reales de HR o attendance está fuera del alcance de la verificación.
El código de ejemplo, los nombres de archivo y las claves de entrada se mantienen como en el original. Consulta también los comandos y los pasos de comprobación del texto traducido.
Material de práctica original · Guarda los archivos originales por separado antes de ejecutarlo.
Fuentes de referencia
Las explicaciones y los ejemplos son de elaboración propia. Puedes consultar los comportamientos y conceptos relacionados en las siguientes fuentes oficiales.
Encuentra filas con coincidencia exacta en las cuatro columnas y recopílalas en un nuevo archivo de Excel junto con sus números de fila originales. Revisa cada grupo de duplicados, incluida su primera aparición.
Antes de convertir una tabla en texto, alinea el objeto, el periodo, el denominador y la unidad. Usa una tabla sintética de resultados mensuales para comprobar un valor que cambió de 80 % a 82 % y una conversión de unidades, y después crea una lista de comprobación reutilizable.
Combina hojas de cálculo que utilizan las mismas columnas en una única tabla de Excel, registrando de qué hoja procede cada fila. Un pequeño libro sintético permite verificar manualmente el orden de las filas, los recuentos y los totales.
Combina varios PDFs en un solo archivo, extrae un intervalo inclusivo de páginas en un segundo archivo y verifica el número y el orden de las páginas resultantes. Un conjunto sintético de páginas en blanco con dimensiones deliberadamente diferentes permite comprobar el resultado manualmente.