Created
April 12, 2026 22:04
-
-
Save beatrizsmerino/bcaee8778710c7d3154290f1ba0b0b7a to your computer and use it in GitHub Desktop.
Calcula horas reales de jornada con Claude Code (detecta sueño con pausas >=4h)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # Uso: horas-claude.sh [YYYY-MM-DD] (por defecto: hoy) | |
| DAY="${1:-$(date +%Y-%m-%d)}" | |
| python3 - "$DAY" <<'PY' | |
| import json, glob, sys | |
| from datetime import datetime, timezone, timedelta | |
| day = datetime.strptime(sys.argv[1], "%Y-%m-%d").date() | |
| local = datetime.now().astimezone().tzinfo | |
| files = glob.glob('/Users/beatrizsmerino/.claude/projects/*/*.jsonl') | |
| all_ts = [] | |
| for f in files: | |
| try: | |
| with open(f) as fh: | |
| for line in fh: | |
| try: | |
| d = json.loads(line) | |
| t = d.get('timestamp') | |
| if not t: continue | |
| dt = datetime.fromisoformat(t.replace('Z','+00:00')).astimezone(local) | |
| # ventana amplia: día anterior + día + siguiente | |
| if abs((dt.date() - day).days) <= 1: | |
| all_ts.append(dt) | |
| except: pass | |
| except: pass | |
| all_ts.sort() | |
| # Inicio real de la jornada: primera actividad del día tras una pausa >=4h (sueño). | |
| # Si la madrugada es continuación de la noche anterior, no cuenta como inicio. | |
| SLEEP = timedelta(hours=4) | |
| start_idx = None | |
| for i, t in enumerate(all_ts): | |
| if t.date() != day: continue | |
| if i == 0 or (t - all_ts[i-1]) >= SLEEP: | |
| start_idx = i | |
| break | |
| # mismo día pero sin pausa de sueño previa → buscamos la siguiente pausa | |
| # Si no hay pausa >=4h dentro del día, usar primera actividad del día | |
| if start_idx is None: | |
| for i, t in enumerate(all_ts): | |
| if t.date() == day: | |
| start_idx = i | |
| break | |
| if start_idx is None: | |
| print(f"Sin actividad el {day}") | |
| sys.exit(0) | |
| # Fin: última actividad antes de la siguiente pausa de sueño (o fin del array) | |
| end_idx = len(all_ts) - 1 | |
| for i in range(start_idx + 1, len(all_ts)): | |
| if (all_ts[i] - all_ts[i-1]) >= SLEEP: | |
| end_idx = i - 1 | |
| break | |
| ts = all_ts[start_idx:end_idx+1] | |
| GAP = timedelta(minutes=15) | |
| active = timedelta() | |
| breaks = [] | |
| for i in range(1, len(ts)): | |
| g = ts[i] - ts[i-1] | |
| if g < GAP: | |
| active += g | |
| else: | |
| breaks.append((ts[i-1], ts[i], g)) | |
| def hm(td): | |
| s = int(td.total_seconds()) | |
| return f"{s//3600}h {(s%3600)//60}min" | |
| print(f"📅 {day}") | |
| print(f" Inicio: {ts[0].strftime('%H:%M')}") | |
| print(f" Fin: {ts[-1].strftime('%H:%M')}") | |
| print(f" Span total: {hm(ts[-1]-ts[0])}") | |
| print(f" Tiempo activo: {hm(active)} (huecos <15min)") | |
| print(f" Mensajes: {len(ts)}") | |
| print(f" Pausas largas: {len(breaks)}") | |
| for a,b,g in breaks: | |
| print(f" {a.strftime('%H:%M')} → {b.strftime('%H:%M')} ({hm(g)})") | |
| # Sugerencia descanso: regla 50/10 | |
| ideal_breaks = int(active.total_seconds() // 3000) # 50min | |
| done = sum(1 for _,_,g in breaks if g >= timedelta(minutes=10)) | |
| print(f"\n💡 Para {hm(active)} activo: ideal ~{ideal_breaks} pausas de 10min. Hechas: {done}") | |
| PY |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment