omar a496b1c672 fix: el jitter era demasiado regular; planificar por adelantado
El 96% de las entradas caian en solo dos minutos (08:58 / 08:59). El historial
real del usuario tiene 4 valores repartidos, o sea que el bot resultaba MAS
regular que una persona.

Tres cosas estaban mal:

- La deriva de la costumbre se aplicaba SUMANDO segundos. Cuando salia negativa
  hundia el valor contra el suelo y amontonaba medio año en el mismo minuto,
  justo lo contrario de lo que pretendia. Ahora va en escala logaritmica:
  multiplica el centro en vez de sumarse. Solo con eso la moda baja del 50% al
  39%, sin coste ninguno.

- La distribucion diaria era uniforme. Una lognormal da mas minutos distintos
  por menos presencia extra, porque la mayoria de los dias caen baratos cerca de
  la hora y solo unos pocos se van lejos. Ademas es la forma real de la llegada
  de alguien que intenta ser puntual.

- El tope recortaba con min(), asi que un 3% de los dias caia exactamente en el
  limite y se veia como un muro en el histograma. Ahora resortea.

El compromiso de fondo no tiene truco: solo se puede variar hacia atras, y
alejarse cuesta presencia. Perfiles medidos sobre los 226 dias laborables del
año, los tres con saldo 0:

    discreto    2,8 min/dia   entrada 2 minutos, moda 75%
    natural     4,2 min/dia   entrada 5 minutos, moda 51%   <- por defecto
    camuflado   5,5 min/dia   entrada 4 minutos, moda 43%

Ademas, /proximos mostraba una banda estimada que ocultaba la cola. Ahora se
planifican 21 dias por adelantado y se muestran las horas exactas, con lo que
la prevision es real y se puede decidir sobre ella. El plan guardado se
revalida contra el calendario de Timenet y se regenera si cambia el tipo de
jornada de un dia. Google Calendar se consulta con una sola autenticacion para
todo el horizonte.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 19:36:38 +02:00
2021-10-15 14:41:56 +02:00

Autoficher para Timenet

Ficha y desficha automáticamente en Timenet, controlado desde Telegram.

Cómo calcula Timenet (y por qué importa)

Verificado sobre 6 meses de historial (104 días laborables, 376 fichajes):

trabajado = Σ  min(salida, fin_teórico) − max(entrada, inicio_teórico)

Timenet recorta a la ventana teórica. La API guarda dos horas por fichaje: HR (la que fichaste) y H (la que te cuentan). De 376 fichajes, 136 llevaban ajuste: todos salidas, todos recortados hacia atrás. Ninguna entrada ajustada jamás.

Consecuencias, y son todo el diseño de este programa:

  • Sólo importan las entradas. Llegar tarde se debita al minuto; salir tarde no acredita nada. Por eso las entradas se fichan antes de la hora y las salidas después.
  • Acabar el mes en positivo es imposible. 0 días con saldo positivo en 108, y timeExtraHours: false en todos los tipos de jornada. El objetivo es 0 clavado.
  • El calendario laboral (festivos, vacaciones, horarios) lo da cp/calendar. No hay que adivinarlo. Google Calendar queda sólo para ausencias personales.

Servicio (recomendado)

pip install -r requirements.txt          # requiere python-telegram-bot >= 22
cp sample.config.json config.json        # y edítalo
echo "TIMENET_PIN=1234" > .env && chmod 600 .env

cp deploy/autoficher.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now autoficher.service
sudo loginctl enable-linger $USER        # para que sobreviva al logout y al reinicio
systemctl --user status autoficher        # estado
journalctl --user -u autoficher -f        # logs en vivo
systemctl --user restart autoficher       # recargar tras tocar config

Comandos de Telegram

Se registran solos en el menú del chat al arrancar.

comando qué hace
/hoy plan y estado de hoy, con lo que Timenet ha registrado de verdad
/saldo saldo del mes y acumulado
/plan plan de hoy, con botón para re-tirar los dados
/proximos [n] previsión de los próximos días laborables y sus rangos
/fichar /desfichar fichaje manual inmediato
/off [hoy|dd/mm|dd/mm-dd/mm] no fichar esos días
/on [fecha] volver a fichar ese día
/pausa /reanudar parar o retomar el servicio
/log últimos eventos

Doce minutos antes de cada fichaje llega un aviso con killswitch:

⏳ ENTRADA programada a las 08:59:22 (en 12 min)
[🛑 Cancelar hoy]  [⏭ +15 min]  [✅ Fichar ya]

El bot sólo atiende al chat_id de config.json; cualquier otro chat se ignora.

Configuración

Lo esencial va en config.json (ver sample.config.json). Los valores por defecto del jitter están en app/settings.py:

perfil entry_before / exit_after (s) presencia extra colchón mínimo
holgado 40-150 / 30-130 5,67 min/día 30 s
equilibrado (por defecto) 30-100 / 20-70 3,55 min/día 20 s
ajustado 20-70 / 15-45 2,70 min/día 15 s

Los tres dan saldo 0; cambia cuánta presencia real cuestan y cuánto retraso del daemon aguantan antes de perder un minuto.

geo_jitter_meters desplaza la posición dentro de un disco de ese radio. Sin él, las coordenadas salen idénticas al sexto decimal en todos los fichajes.

Herramientas

./venv/bin/python3 tools/tn_inspect.py     # calendario y saldo, contra la API real
./venv/bin/python3 tools/dryrun.py         # simula el año y verifica que no pierde minutos
./venv/bin/python3 tools/test_daemon.py    # 19 pruebas con reloj simulado, sin red

Modo antiguo (CLI de un disparo)

main.py sigue funcionando para fichajes puntuales desde consola o cron:

python3 main.py -p <pin> [-t <0=Entrada, 1=Salida> | -bt] [-f] [-d]

No aplica la corrección del sesgo ni consulta el calendario de Timenet: pierde de 3 a 5 minutos al día. Úsalo sólo como respaldo manual.

Google Calendar

Para ausencias personales. Descarga el OAuth de la API de Google y déjalo en la raíz como googleoauth_grant.json; el token se guarda en googleoauth.json. Se consulta el calendario autoficher: si un evento cubre el día, no se ficha.

Description
No description provided
Readme 23 MiB
Languages
Python 100%