Commit Graph

51 Commits

Author SHA1 Message Date
f2ed694f98 feat: comando /grafico con cinco visualizaciones
Cinco graficos generados con matplotlib y enviados como foto a Telegram, con
botones para elegir. La paleta es la de referencia del proyecto, validada con el
comprobador de daltonismo en claro y oscuro (peor par CVD delta-E 24.7 / 26.8,
umbral 8).

- saldo        acumulado (linea) + saldo de cada dia (barras), dos paneles
- puntualidad  desviacion de cada entrada en el tiempo. Es el que explica el
               problema de un vistazo: en 6 meses no hay ni un solo punto por
               debajo de cero, o sea que nunca se ha llegado antes de la hora
- horas        histograma de la hora de entrada, historico frente a planificado.
               Las dos distribuciones no se solapan en absoluto
- regalado     minutos al mes presente sin que corra el reloj
- prevision    linea de tiempo de los proximos dias con la franja que cuenta

Un PNG no tiene hover, asi que las cifras que en una pagina resolveria el
tooltip van en el pie de foto y en etiquetas directas sobre las marcas.

Al mirarlos renderizados aparecieron cuatro defectos que el codigo no delata:
titulo y subtitulo pisandose, la etiqueta del peor dia chocando con el eje de
fechas, un dia de +64 min aplastando el resto del eje contra el cero, y un bug
de formato que pintaba "09:64" en vez de "10:04". Corregidos: ejes acotados con
los valores extremos anotados aparte, y las horas formateadas con un helper.

Executor.historico() extrae los dias de varios meses ya parseados; el servicio
lo cachea media hora porque son seis llamadas a la API.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 20:05:20 +02:00
ba0d6b5f08 feat: horizonte de 35 dias y histograma real en /perfil
El cambio de perfil funcionaba, pero con 13 dias planificados por delante la
diferencia entre perfiles no se apreciaba: la variedad es una propiedad
estadistica y con esa muestra un 51% y un 29% se ven casi igual.

- Horizonte de 21 a 35 dias naturales (~22 laborables). Con esa muestra el
  histograma ya se corresponde con lo anunciado: disperso da 6 horas distintas
  con la mas repetida al 27%, frente al 29% medido sobre el año entero.

- /perfil enseña ahora el histograma REAL de lo que hay sorteado, no solo las
  cifras teoricas del perfil. Asi se ve el efecto del cambio en el momento, que
  era justo lo que faltaba para poder decidir.

Verificado que los cinco perfiles siguen dando exactamente las cifras
documentadas tras el refactor a perfiles por tramo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 19:55:13 +02:00
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
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
365f676a1f feat: comando /proximos con la prevision de los proximos dias
Muestra que dias se fichara y cuales no (festivo, vacaciones, override o
ausencia de Google Calendar), con el saldo acumulado al final.

Para los dias futuros no se inventa una hora exacta: el instante se sortea esa
madrugada y la costumbre deriva de un dia al siguiente, asi que se muestra el
RANGO en que caera cada fichaje, que si es exacto. Para hoy se muestra el plan
real con su estado.

De paso, nombres de dia en castellano en /hoy y /proximos: dependian del locale
del sistema y salian en ingles.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 18:57:48 +02:00
5ba5710db2 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>
2026-09-24 18:51:46 +02:00
9294474066 feat: nucleo nuevo: cliente API, calendario laboral, planificador y jitter
Timenet recorta el tiempo a la ventana teorica del horario:

    trabajado = min(salida, fin_teorico) - max(entrada, inicio_teorico)

Verificado sobre 6 meses (104 dias laborables, 376 fichajes). La API guarda dos
horas por fichaje: HR (la que fichas) y H (la que cuenta). De 376 fichajes, 136
llevaban ajuste: todos salidas, todos recortados hacia atras. Ninguna entrada
ajustada jamas. Resultado: una entrada tardia se debita al minuto y una salida
tardia no acredita nada, asi que el jitter aleatorio positivo que habia perdia
unos 3,2 min/dia (-776 min acumulados, casi 13 horas).

Y acabar el mes en positivo es imposible: 0 dias con saldo positivo en 108, y
timeExtraHours=false en todos los tipos de jornada. El objetivo es 0 clavado.

- timenet_api: cliente limpio. Documenta que Tn-D y dStr son obligatorias (sin
  ellas el servidor responde 403 hasta en endpoints publicos).
- schedule: calendario laboral desde cp/calendar. Festivos, vacaciones y tramos
  horarios los da Timenet, no hace falta adivinarlos.
- planner + jitter: entradas siempre ANTES de la hora, salidas siempre DESPUES,
  con costumbre que deriva lenta y cola larga hacia el lado seguro. Invariantes
  que fallan en duro si una entrada se planifica tarde.
- jitter_geo: las coordenadas del historico son identicas al sexto decimal en
  los 376 fichajes; ahora se desplazan dentro de un disco.
- store: SQLite con plan diario, overrides, auditoria y estado del drift.
- settings: config sin el acoplamiento a argparse del config_parser original.

tools/dryrun.py simula el año entero: saldo +0 en 226 dias laborables, con
~3,5 min/dia de presencia extra.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 18:51:33 +02:00
51976fbe22 fix: corregir bugs que costaban minutos y tapaban errores
- google_calendar: RefreshError se referenciaba como
  google.oauth2.credentials.exceptions.RefreshError, ruta que no existe. Al
  fallar un refresh de token saltaba un AttributeError que ocultaba el error
  real y rompia el re-auth. Ahora se importa de google.auth.exceptions.

- google_calendar: getEvent() comprobaba si habia un evento activo en ese
  instante. El daemon planifica de madrugada, asi que una ausencia de 09:00 a
  18:00 no se detectaba y se fichaba igual. Ahora acepta un dia y comprueba
  solapamiento. Sin argumento mantiene el comportamiento anterior.

- google_calendar: datetime.utcnow() esta deprecado en Python 3.12+.

- timenet_manager: "args.basedtime and not args.type" ignoraba -t 0 (Entrada)
  al combinarlo con -bt, porque not 0 es True. Ahora es "args.type is None".

- timenet_manager: updateVersion() asignaba el objeto Response al header Tn-V
  en vez de su texto.

- timenet_manager: geo.accuracy se parseaba y se podia sobrescribir por CLI
  pero nunca se enviaba en el payload de cp/checks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 18:50:59 +02:00
88b56a78a0 requirements
Signed-off-by: omar <admin@pribyte.cloud>
2026-09-06 22:34:42 +02:00
omar
fbfcf8fec0 remove venv 2026-03-27 01:23:39 +01:00
Omar Sánchez Pizarro
ce179ebff9 Agregar logging estructurado, try-catches y envío de errores por Telegram
- Agregado logging estructurado en todos los módulos
- Implementados try-catches en todos los métodos críticos
- Errores ahora se envían automáticamente por Telegram
- Mejorado manejo de excepciones en requests HTTP
- Agregado try-catch global en main.py para errores no manejados
- Logging detallado en timenet_manager, telegram_bot, google_calendar y config_parser
2026-01-13 01:39:16 +01:00
Omar Sánchez Pizarro
c052439de3 now working again
Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
2025-12-02 09:49:01 +01:00
Omar Sánchez Pizarro
5929be384a fix: add sleep commented
Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
2025-12-01 20:16:12 +01:00
Omar Sánchez Pizarro
b612fcb4ac feat: now check last check
Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
2025-12-01 20:03:46 +01:00
Omar Sánchez Pizarro
5fdfbf1927 fix: headers
Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
2025-12-01 17:37:26 +01:00
Omar Sánchez Pizarro
1612510301 feat: upgrade to new api version
Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
2025-12-01 17:25:36 +01:00
8123de824e fixes y calendario de google holidays desactivado
Signed-off-by: Omar Sánchez Pizarro <admin@pribyte.cloud>
2025-09-04 14:13:57 +02:00
Omar Sánchez Pizarro
e9ec438183 cosas bonitas con el fiching
Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
2023-11-09 18:48:07 +01:00
Omar Sánchez Pizarro
1f3f57e3eb send report after errors
Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
2023-10-27 15:19:36 +02:00
Omar Sánchez Pizarro
6159ec68ae fix time check of timenet api when need sleep after web request.
Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
2023-10-27 15:16:50 +02:00
e63027cf24 delete __pycache__ 2023-10-27 15:13:44 +02:00
90a2bf4578 sleep not working 2023-10-27 15:12:40 +02:00
Omar Sánchez Pizarro
6020ce5acb send message async 2023-10-09 14:55:54 +02:00
Omar Sánchez Pizarro
4f1aea3139 sleep 2023-10-09 14:23:49 +02:00
Omar Sánchez Pizarro
d09c66b125 particionado 2023-10-09 14:13:36 +02:00
Omar Sánchez Pizarro
927c281631 separación de codigo para facil edición
Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
2023-08-02 10:06:48 +02:00
Omar Sánchez Pizarro
76cf03f883 changes and fixes
Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
2023-05-25 11:35:05 +02:00
Omar Sánchez Pizarro
73dc43a193 google calendar
Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
2022-10-25 13:10:55 +02:00
Omar Sánchez
321f88f16a fix emoji order 2021-10-20 13:25:01 +02:00
Omar Sánchez
ab6855259e add emojis 2021-10-20 12:53:34 +02:00
Omar Sánchez
9859ccde8a resolve config.json root path 2021-10-20 12:43:51 +02:00
Omar Sánchez
40c574f4fc report fix 2021-10-20 10:04:14 +02:00
Omar Sánchez
e66d36591e solve errors 2021-10-18 09:12:42 +02:00
Omar Sánchez
86de92e356 change arg types 2021-10-15 14:50:38 +02:00
Omar Sánchez
cf7026cb3b geo accuracy on config 2021-10-15 14:41:56 +02:00
Omar Sánchez
814efd2391 enable post to check :/ 2021-10-15 14:22:59 +02:00
Omar Sánchez
585a8b9a5d config replacement form arguments 2021-10-15 14:21:13 +02:00
Omar Sánchez
51a2c1153e add requirements 2021-10-14 12:52:18 +02:00
Omar Sánchez
025355b9d2 update requirements y solución a mensajes de reporte 2021-10-13 17:46:25 +02:00
Omar Sánchez
d546afb795 Telegram messages to report 2021-10-13 09:54:01 +02:00
Omar Sánchez
5719c8ffe5 Telegram messages to report 2021-10-13 09:52:42 +02:00
93a3766f3f Delete config.json 2021-10-08 14:10:00 +02:00
Omar Sánchez
6b7a7a24e4 json config 2021-10-08 14:09:18 +02:00
Omar Sánchez
f212b586b0 Final version sample config 2021-10-05 10:04:48 +02:00
Omar Sánchez
d23038c3c5 Final version 2021-10-05 09:59:14 +02:00
7f24b1b5b9 Merge branch 'master' of https://git.pribyte.cloud/root/autoficher 2019-12-04 15:26:45 +01:00
4a6b94e1a2 old version 2019-10-23 01:57:30 +02:00
Omar Sánchez Pizarro
367b0fb254 change default port 2019-05-08 01:21:00 +02:00
Omar Sánchez Pizarro
1bec326550 -argumentos + ayuda 2019-05-04 03:26:20 +02:00
Omar Sánchez Pizarro
bf66acb2d0 corrección de errores + documentación 2019-05-04 02:30:32 +02:00