feat: daemon con bot de Telegram, killswitch y servicio systemd
Pasa de un cron ciego a un servicio en segundo plano. El killswitch lo exige: para poder cancelar un fichaje pendiente desde el movil tiene que haber un proceso escuchando durante la espera. - daemon: bucle propio que duerme exactamente hasta el siguiente evento, con precision de decimas. No usa JobQueue de PTB porque APScheduler 3.6 importa pkg_resources, que ya no existe en Python 3.13; ademas asi no se añade una dependencia. Recupera fichajes perdidos si el equipo estuvo apagado. - bot: /hoy /saldo /plan /fichar /desfichar /off /on /pausa /reanudar /log. Los comandos se registran con setMyCommands para que salgan en el menu del chat. Solo atiende al chat_id configurado. - killswitch: aviso 12 min antes con teclado inline (cancelar / +15 min / fichar ya). - Google Calendar queda solo para ausencias personales; festivos y vacaciones salen del calendario de Timenet. - deploy/autoficher.service: unidad de usuario endurecida (ProtectSystem strict, escritura solo en el propio directorio), Restart=always. - requirements: python-telegram-bot >= 22. La 20.5 tiene un bug de __slots__ en Updater que rompe el polling en Python 3.13. - .gitignore: data/, *.db, .env, __pycache__ en cualquier nivel. tools/test_daemon.py cubre con reloj simulado y sin red: plan, aviso previo, fichaje a su hora, killswitch, retraso de 15 min, pausa, recuperacion tras apagon, fallo de red y dia no laborable. 19 comprobaciones. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
136
README.md
136
README.md
@@ -1,26 +1,118 @@
|
||||
Autoficher para Timenet
|
||||
==============================
|
||||
=======================
|
||||
|
||||
Que es esto?
|
||||
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 |
|
||||
| `/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
|
||||
------------
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
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
|
||||
---------------
|
||||
Aplicación simple para fichar y desfichar automáticamente
|
||||
|
||||
Como se usa?
|
||||
---------------
|
||||
1. Ejecutamos `pip3 install -r requirements.txt` para instalar los componentes
|
||||
2. Ejecutamos `python3 ./autofitcher.py -u <user> -p <pin> [-t <0 = Entrada, 1 = Salida>| -bt]`
|
||||
3. Opcionalmente podemos ponerlo en un Crontab de linux para que se ejecute cada X tiempo y se realize el fichado automatico
|
||||
Parametros obligatorios
|
||||
---------------
|
||||
- -u > Usuario de timenet (Pista Cero = 4c26cc59-ee52-4c47-b7fe-1065a5e5bf84) (Lo he pasado a config.json)
|
||||
- -p > Pin de usuario
|
||||
|
||||
Tendremos que escojer también entre --basedtime o --type
|
||||
|
||||
- -bt > No requiere argumento. Esto ficha y desficha basado en la configuración de horas a seguir
|
||||
- -t <0 = Entrada, 1 = Salida> > Forzamos estado al entrar/salir
|
||||
|
||||
Calendario google
|
||||
---------------
|
||||
Para añadir el uso de calendario de google debemos de descargar el archivo de acceso oauth API y añadirlo
|
||||
a la raiz del proyecto como "googleoauth.json" se revisará calendario de festivos en españa y calendario autoficher.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user