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>
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>
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>