Files
autoficher/README.md
omar a1eef6472f feat: perfiles de jitter intercambiables desde Telegram
Cinco perfiles con distinto punto en el compromiso entre variedad visible y
minutos de presencia de mas. Todos dan saldo 0. Se cambian en caliente con
/perfil, con botones, y al cambiar se vuelven a tirar los dados de todos los
dias del horizonte que no se hayan fichado ya.

                 min/dia  madrugon  entrada 09:00
    discreto       2,8      1,3     2 minutos, moda 75%
    natural        4,1      1,8     5 minutos, moda 51%   <- por defecto
    camuflado      5,5      2,1     4 minutos, moda 43%
    disperso       7,9      3,0     6 minutos, moda 29%
    sin_madrugar   5,7      1,3     2 minutos, moda 77%
    (historico real del usuario:    4 minutos, moda 38%)

La distincion util no es solo el coste total sino DONDE cae. Adelantar la
entrada de la mañana alarga el dia; adelantar la vuelta de comer solo acorta la
comida y se sale a la misma hora. De ahi "sin_madrugar": entrada de la mañana lo
mas pegada posible a las 9 (madrugon 1,26 min/dia, el mas bajo de todos, por
debajo incluso de "discreto") y toda la variedad en la pausa y las salidas.

Para eso los perfiles admiten afinar por tramo con sufijo _w<n>:
entry_median_w0 gana a entry_median para el tramo de la mañana.

tools/perfiles.py mide cada perfil sobre los 226 dias laborables del calendario
real; las cifras de arriba salen de ahi y van guardadas como metadatos para que
/perfil las muestre sin recalcular.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 19:41:50 +02:00

137 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
----------------------
```bash
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
```
```bash
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 |
| `/perfil [nombre]` | ver y cambiar el perfil de jitter |
| `/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.
Perfiles de jitter
------------------
Timenet sólo cuenta dentro de la ventana teórica, así que las horas de fichaje
sólo pueden variar hacia el lado seguro, y alejarse cuesta presencia real. Más
variedad visible = más minutos ahí. Cada perfil elige un punto de ese compromiso.
Los cinco dan **saldo 0**; se cambian en caliente con `/perfil`.
| perfil | min/día | madrugón | entrada 09:00 |
|---|---|---|---|
| discreto | 2,8 | 1,3 | 2 minutos, moda 75 % |
| **natural** (por defecto) | 4,1 | 1,8 | 5 minutos, moda 51 % |
| camuflado | 5,5 | 2,1 | 4 minutos, moda 43 % |
| disperso | 7,9 | 3,0 | 6 minutos, moda 29 % |
| sin madrugar | 5,7 | **1,3** | 2 minutos, moda 77 % |
| *histórico real* | — | — | *4 minutos, moda 38 %* |
`min/día` es la presencia total de más. `madrugón` es la parte de esos minutos
que te levantas antes por la entrada de la mañana — el resto se lo come la pausa
de comer y la salida, que no alargan el día. Por eso existe *sin madrugar*:
entrada de la mañana pegada a las 9 y toda la variedad en la comida y la salida.
Medido sobre los 226 días laborables del año con `tools/perfiles.py`. Un perfil
puede afinar por tramo con sufijo `_w<n>`: `entry_median_w0` gana a `entry_median`
para el tramo de la mañana.
Configuración
-------------
Lo esencial va en `config.json` (ver `sample.config.json`). `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
------------
```bash
./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
./venv/bin/python3 tools/perfiles.py # mide cada perfil sobre el calendario real
```
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.