Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,14 @@ This project is an automated bot that monitors the status of Buenos Aires subway
| `TELEGRAM_TOKEN` | Your Telegram bot token | - |
| `TELEGRAM_CHAT_ID` | Chat ID where alerts are sent | - |
| `intervalo_ejecucion` | Interval between checks (seconds) | 5400 |
| `horario_analisis_inicio` | Monitoring start hour | 6 |
| `horario_analisis_fin` | Monitoring end hour | 23 |
| `horario_analisis_inicio` | Monitoring start hour (Buenos Aires time) | 6 |
| `horario_analisis_fin` | Monitoring end hour (Buenos Aires time) | 23 |
| `umbral_obra_programada` | Detections to classify as work | 5 |
| `dias_renotificar_obra` | Days between work reminders | 15 |
| `dias_limpiar_historial` | Days to clean old history | 5 |

**Note on timezones:** The bot uses Buenos Aires timezone (America/Argentina/Buenos_Aires, UTC-3) for monitoring, regardless of the server's timezone where it runs. This ensures that the configured hours are respected correctly even when deployed on servers with different timezones (like Zeabur which uses UTC).

## Credits

- Developed by Agustin Monetti.
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,14 @@ Este proyecto es un bot automatizado que monitorea el estado de las líneas del
| `TELEGRAM_TOKEN` | Token de tu bot de Telegram | - |
| `TELEGRAM_CHAT_ID` | ID del chat donde envía alertas | - |
| `intervalo_ejecucion` | Intervalo entre verificaciones (segundos) | 5400 |
| `horario_analisis_inicio` | Hora de inicio del monitoreo | 6 |
| `horario_analisis_fin` | Hora de fin del monitoreo | 23 |
| `horario_analisis_inicio` | Hora de inicio del monitoreo (hora de Buenos Aires) | 6 |
| `horario_analisis_fin` | Hora de fin del monitoreo (hora de Buenos Aires) | 23 |
| `umbral_obra_programada` | Detecciones para clasificar como obra | 5 |
| `dias_renotificar_obra` | Días entre recordatorios de obras | 15 |
| `dias_limpiar_historial` | Días para limpiar historial antiguo | 5 |

**Nota sobre zonas horarias:** El bot utiliza la zona horaria de Buenos Aires (America/Argentina/Buenos_Aires, UTC-3) para el monitoreo, independientemente de la zona horaria del servidor donde se ejecute. Esto asegura que los horarios configurados se respeten correctamente incluso cuando se despliega en servidores con zonas horarias diferentes (como Zeabur que usa UTC).

## Créditos

- Desarrollado por Agustin Monetti.
Expand Down
4 changes: 3 additions & 1 deletion config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
from dotenv import load_dotenv
from zoneinfo import ZoneInfo

load_dotenv()
telegram_token = os.getenv('TELEGRAM_TOKEN')
Expand All @@ -14,4 +15,5 @@
estado_normal = "Normal"
estado_redundante = "Servicio finalizado"
horario_analisis_inicio = int(os.getenv('horario_analisis_inicio', 6))
horario_analisis_fin = int(os.getenv('horario_analisis_fin', 23))
horario_analisis_fin = int(os.getenv('horario_analisis_fin', 23))
timezone_local = ZoneInfo('America/Argentina/Buenos_Aires')
31 changes: 16 additions & 15 deletions subte_alerta.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
telegram_token, telegram_chat_id, estado_normal, url_estado_subte,
intervalo_ejecucion, umbral_obra_programada, dias_renotificar_obra,
archivo_estado, estado_redundante, dias_limpiar_historial,
horario_analisis_inicio, horario_analisis_fin
horario_analisis_inicio, horario_analisis_fin, timezone_local
)


Expand Down Expand Up @@ -70,7 +70,7 @@ def guardar_estados(estados_actuales, historial):
"""Guarda el estado actual y el historial en un archivo JSON"""
try:
data = {
"ultima_actualizacion": datetime.now().isoformat(),
"ultima_actualizacion": datetime.now(timezone_local).isoformat(),
"estados_actuales": estados_actuales,
"historial": historial
}
Expand Down Expand Up @@ -182,7 +182,7 @@ def procesar_obra_individual(linea, obra, indice, historial):
"linea_original": linea,
"tipo": "obra",
"contador": 1,
"primera_deteccion": datetime.now().isoformat(),
"primera_deteccion": datetime.now(timezone_local).isoformat(),
"ultima_notificacion": None,
"es_obra_programada": True,
"detectada_por_texto": True,
Expand All @@ -197,15 +197,15 @@ def procesar_obra_individual(linea, obra, indice, historial):

if not historial[clave_obra].get("activa", True):
historial[clave_obra]["activa"] = True
historial[clave_obra]["fecha_reactivacion"] = datetime.now().isoformat()
historial[clave_obra]["fecha_reactivacion"] = datetime.now(timezone_local).isoformat()
return "reactivada_silenciosa", obra

elif (historial[clave_obra]["es_obra_programada"] and
historial[clave_obra].get("ya_notificada", False)):
ultima_notif = historial[clave_obra]["ultima_notificacion"]
if ultima_notif:
ultima_fecha = datetime.fromisoformat(ultima_notif)
if datetime.now() - ultima_fecha >= timedelta(days=dias_renotificar_obra):
if datetime.now(timezone_local) - ultima_fecha >= timedelta(days=dias_renotificar_obra):
return "renotificar", obra
Comment on lines 205 to 209
Copy link

Copilot AI Oct 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mixing timezone-aware and naive datetimes can raise TypeError here if ultima_notificacion was stored previously without a timezone offset. Ensure ultima_fecha is timezone-aware (in timezone_local) before subtracting, e.g., localize when tzinfo is None.

Copilot uses AI. Check for mistakes.
Comment on lines 205 to 209
Copy link

Copilot AI Oct 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ultima_fecha parsed from historical data may be offset-naive (pre-change values saved without tz). Subtracting an aware datetime (ahora_ba()) from a naive datetime raises TypeError. Normalize parsed timestamps by assigning a timezone when tzinfo is None (e.g., assume UTC and convert to timezone_local) before subtracting. For example: 'ultima_fecha = ultima_fecha.replace(tzinfo=timezone.utc).astimezone(timezone_local)'. You may need 'from datetime import timezone' at the top of the file.

Copilot uses AI. Check for mistakes.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove ahora_ba

return "continua", obra
else:
Expand All @@ -215,7 +215,7 @@ def procesar_obra_individual(linea, obra, indice, historial):
"linea_original": linea,
"tipo": "obra",
"contador": 1,
"primera_deteccion": datetime.now().isoformat(),
"primera_deteccion": datetime.now(timezone_local).isoformat(),
"ultima_notificacion": None,
"es_obra_programada": True,
"detectada_por_texto": True,
Expand All @@ -234,7 +234,7 @@ def procesar_problema_individual(linea, problema, indice, historial):
"linea_original": linea,
"tipo": "problema",
"contador": 1,
"primera_deteccion": datetime.now().isoformat(),
"primera_deteccion": datetime.now(timezone_local).isoformat(),
"ultima_notificacion": None,
"es_obra_programada": False,
"detectada_por_texto": False,
Expand All @@ -248,7 +248,7 @@ def procesar_problema_individual(linea, problema, indice, historial):

if not historial[clave_problema].get("activa", True):
historial[clave_problema]["activa"] = True
historial[clave_problema]["fecha_reactivacion"] = datetime.now().isoformat()
historial[clave_problema]["fecha_reactivacion"] = datetime.now(timezone_local).isoformat()
return "problema_reactivado", problema

if (historial[clave_problema]["contador"] >= umbral_obra_programada and
Expand All @@ -267,7 +267,7 @@ def procesar_problema_individual(linea, problema, indice, historial):
"linea_original": linea,
"tipo": "problema",
"contador": 1,
"primera_deteccion": datetime.now().isoformat(),
"primera_deteccion": datetime.now(timezone_local).isoformat(),
"ultima_notificacion": None,
"es_obra_programada": False,
"detectada_por_texto": False,
Expand Down Expand Up @@ -303,7 +303,7 @@ def detectar_componentes_desaparecidos(linea, componentes, historial):
del historial[clave]
else:
historial[clave]["activa"] = False
historial[clave]["fecha_desaparicion"] = datetime.now().isoformat()
historial[clave]["fecha_desaparicion"] = datetime.now(timezone_local).isoformat()

return cambios_resueltos

Expand Down Expand Up @@ -366,7 +366,7 @@ def procesar_linea_normal(linea, historial):

def actualizar_timestamps_notificacion(cambios_nuevos, obras_programadas, obras_renotificar, historial):
"""Actualiza los timestamps de notificación"""
ahora = datetime.now().isoformat()
ahora = datetime.now(timezone_local).isoformat()

for linea_dict in [cambios_nuevos, obras_programadas, obras_renotificar]:
for linea in linea_dict.keys():
Expand All @@ -378,7 +378,7 @@ def limpiar_historial_antiguo(historial):
"""Elimina entradas inactivas después de X días,
incluyendo obras clasificadas por persistencia"""
claves_a_eliminar = []
ahora = datetime.now()
ahora = datetime.now(timezone_local)

for clave, datos in historial.items():
if not datos.get("activa", True):
Expand Down Expand Up @@ -564,14 +564,15 @@ def enviar_mensaje_telegram(mensaje):
def horarios_de_analisis():
"""Determino el horario de análisis
horario de 6 am - 23 pm (definido desde config.py)
Usa timezone de Buenos Aires para evitar problemas con servidores UTC
"""
hora_actual = datetime.now().hour
hora_actual = datetime.now(timezone_local).hour
Copy link

Copilot AI Oct 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Use the new helper for consistency and a single source of time: replace with ahora_ba().hour. The same applies to other direct calls (e.g., lines 579 and 602) to reduce duplication and centralize timezone usage.

Copilot uses AI. Check for mistakes.
return (horario_analisis_inicio <= hora_actual <= horario_analisis_fin)

def verificar_estados():
"""Función principal que verifica los estados y envía alertas si es necesario"""
try:
print(f"Iniciando verificación - {time.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Iniciando verificación - {datetime.now(timezone_local).strftime('%Y-%m-%d %H:%M:%S')}")
estados = obtener_estado_subte()

if not estados:
Expand All @@ -594,7 +595,7 @@ def verificar_estados():
def main():
"""Función principal que ejecuta el ciclo de verificación periódica"""
while True:
hora_actual = datetime.now().hour
hora_actual = datetime.now(timezone_local).hour
Copy link

Copilot AI Oct 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Use ahora_ba() for consistency: 'hora_actual = ahora_ba().hour'.

Suggested change
hora_actual = datetime.now(timezone_local).hour
hora_actual = ahora_ba().hour

Copilot uses AI. Check for mistakes.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no me gusta esa funcion "ahora_ba", la quiero fuera de mi codigo

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usar la libreria "zoneinfo"? en lugar de agregar otra dependencia a requirents.txt?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usar la libreria "zoneinfo"? en lugar de agregar otra dependencia a requirents.txt?

@copilot

if horarios_de_analisis():
print(f"Nos encontramos dentro del horario de analisis - Hora actual {hora_actual} hs")
verificar_estados()
Expand Down