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>
137 lines
5.2 KiB
Markdown
137 lines
5.2 KiB
Markdown
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.
|