#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
===============================================================================
 Archivo:        Main service manager.py
 Autor:           (TU_EMPRESA)
 Fecha creación: AAAA-MM-DD
 Versión:        VERSIÓN_INICIAL
===============================================================================
 Descripción:
    Breve descripción del propósito del archivo y su funcionalidad

===============================================================================
 Copyright:
    © AÑO TU_EMPRESA. Todos los derechos reservados.
    Este archivo forma parte de NOMBRE_PROYECTO y se distribuye bajo
    los términos de la licencia TIPO_LICENCIA.

===============================================================================
 Interface Change List:
    Versión     Fecha        Autor                Descripción del cambio
    ---------   ----------   ------------------   ---------------------------
    vX.X.X    AAAA-MM-DD TU_NOMBRE   Descripción breve
    vX.X.X    AAAA-MM-DD TU_NOMBRE   Descripción breve
===============================================================================
"""

__all__ = ["ExampleClass", "main"]

# =============================================================================
# Importación de librerías
# =============================================================================

# === Importes estándar ===
import sys
import time
import copy
import os
import re
import json
from datetime import datetime, timedelta, timezone
from pprint import pprint 
from collections import defaultdict


# === Importes de terceros ===
import requests
from flask_cors import CORS
from dateutil import parser
from flask import Flask, g, jsonify, request, send_from_directory, send_file, after_this_request, make_response, url_for
from flask_jwt_extended import (JWTManager, create_access_token, create_refresh_token,
                                get_jwt_identity, jwt_required, set_access_cookies, set_refresh_cookies, decode_token)
# from itsdangerous import URLSafeTimedSerializer, SignatureExpired, BadSignature
from werkzeug.security import check_password_hash, generate_password_hash
from pymongo import MongoClient, UpdateOne, InsertOne, errors, DeleteOne, DeleteMany, ReplaceOne
from pymongo.errors import BulkWriteError
# from bson import ObjectId
from bson.objectid import ObjectId
from bson import json_util
# import numpy as np


# === Importes internos ===
IS_LOCAL = False

# Imports para  pruebas locales
if  IS_LOCAL:
    from modules.email_sender import send_automatic_email
    from modules.legacy_consolidado_estados import consolidado_empresa

    from nanoox.nanoox_timeseries import (
                                            tipo_de_datos, 
                                            process_sensor_data,
                                            get_historial_control,
                                            get_generator, 
                                            get_control_by_centro_id,
                                            get_control_version_by_centro_id
                                            )
    
    from nanoox.get_data import get_latest_data_ts
    from nanoox.generate_report import gen_data_report

    
    from nanoox.reports_management_ts_service import _gen_data_report

    from legacy_verificacion_status_servicios_centros import verificacion_status_servicios_por_centro

    from Analytic.Scapp_analytics import (compute_daily_analytic_data, 
                                            compute_multiple_analytic_days, 
                                            compute_analytic_area_data, 
                                            compute_analytic_area_versus_data, 
                                            compute_analytics_by_region
                                        )


# Imports en servidor o produccion
else:

    from .modules.email_sender import send_automatic_email
    from .modules.legacy_consolidado_estados import consolidado_empresa

    
    from .nanoox.nanoox_timeseries import (
                                            tipo_de_datos, 
                                            process_sensor_data,
                                            get_historial_control,
                                            get_generator, 
                                            get_control_by_centro_id,
                                            get_control_version_by_centro_id
                                            )
    
    from .nanoox.get_data import get_latest_data_ts
    from .nanoox.generate_report import gen_data_report

    
    from .nanoox.reports_management_ts_service import _gen_data_report

    from .legacy_verificacion_status_servicios_centros import verificacion_status_servicios_por_centro

    from .Analytic.Scapp_analytics import (compute_daily_analytic_data, 
                                            compute_multiple_analytic_days, 
                                            compute_analytic_area_data, 
                                            compute_analytic_area_versus_data, 
                                            compute_analytics_by_region
                                        )

# =============================================================================
# Constantes y configuraciones globales
# =============================================================================

# Definicion de nombres de bases de datos globales de un solo cliente
user_database_name = "users"
enviro_database_name = "ENVIRO"
nanoox_database_name =  "NANOOX__TS" 
scapp_database_name = "ScappDB" 


HISTORIAL_CONTROL_ARRAY_LEN_TO_SEND = 50
region_analytic_access = ["Gerencia"] #Permisos para acceso a Analítica de Región. Agregar cargo a lista para dar permiso

# =============================================================================
# Definiciones locales
# =============================================================================

class SimpleLogger:
    def info(self, msg):
        print(f"[INFO] {msg}")
        # print(msg)
    def warning(self, msg):
        print(f"[WARNING] {msg}")
        #print(msg)
    def error(self, msg):
        print(f"[ERROR] {msg}")
    
    def debug(self, msg):
        # print(f"[DEBUG] {msg}")
        return


logger = SimpleLogger()


app = Flask(__name__, static_folder='build')            
# Configura CORS
CORS(app,
     origins=['*'], #http://172.16.2.199:3000
     supports_credentials=True,
     methods=['GET','POST','OPTIONS','DELETE'],
     allow_headers=['Content-Type','Authorization']
)




# ----Configurar JWTManager (TOKENS) para seguridad ----#

app.config['JWT_SECRET_KEY']            = 'ITG-Secret-jwt-key'
app.config['JWT_ACCESS_TOKEN_EXPIRES']  = timedelta(minutes=15)   #(minutes=15)  days=1
app.config['JWT_REFRESH_TOKEN_EXPIRES'] = timedelta(days=7) #(days=7)  100
app.config['JWT_RESET_SECRET_KEY'] = 'ITG-Secret-reset-key'           # Clave separada para tokens de restablecimiento
#Config para cookies en FLASK JWT
app.config["JWT_TOKEN_LOCATION"] = ["cookies"]
app.config["JWT_ACCESS_COOKIE_NAME"] = "access_token"
app.config["JWT_REFRESH_COOKIE_NAME"] = "refresh_token"
app.config["JWT_COOKIE_CSRF_PROTECT"] = False
app.config["JWT_COOKIE_SECURE"] = True #True  # Usa False en desarrollo local sin HTTPS
app.config["JWT_COOKIE_SAMESITE"] = "None"  #"Strict" ,  "None" , "Lax"

# inicializar el JWTManager sobre app 
jwt = JWTManager(app)



def get_db():
    """Obtiene una conexion de MongoDB por request."""
    if 'db_client' not in g:
        g.db_client = MongoClient("mongodb://localhost:27017/")
    return g.db_client

def create_directory(directory_path):
    try:
        # Crear el directorio, ignorando el error si ya existe
        os.makedirs(directory_path, exist_ok=True)
        print(f"Directorio '{directory_path}' creado con exito o ya existia.")
    except Exception as e:
        # Manejar cualquier otro error que pueda ocurrir
        print(f"Error al crear el directorio '{directory_path}': {e}")

directorio_actual = 'var/www/itgmanager/itgmanager' # os.getcwd()
# directorio_actual = os.getcwd()

print(f"Directorio actual: {directorio_actual}")
REPORTS_DIRECTORY = os.path.join(directorio_actual, "reportes")#".reportes"
# REPORTS_DIRECTORY = "reportes"
create_directory(REPORTS_DIRECTORY)


# ------Definicion de funciones Auxiliares------ #

# ! -- Servir los archivos estaticos de React -- #
# Funcion para detectar si el User-Agent corresponde a un dispositivo movil
def is_mobile(user_agent_str):
    mobile_regex = r"(android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini)"
    return re.search(mobile_regex, user_agent_str, re.IGNORECASE) is not None


# ! -- Funciones Auxiliares Login -- #

# ---------------------------------------------------
#   CALLBACK para revocacion de tokens basados en JTI
# ---------------------------------------------------
@jwt.token_in_blocklist_loader
def check_if_token_revoked(jwt_header, jwt_payload):
    """
    Se ejecuta para cualquier token (access o refresh).
    Solo revocaremos aqui los refresh tokens cuyo jti no coincida
    con el ultimo almacenado en la BD. Los access tokens
    los dejamos expirar normalmente.
    """
    client = get_db()
    users_collection = client[user_database_name].users

    jti        = jwt_payload["jti"]
    token_type = jwt_payload.get("type", None)  # viene como "access" o "refresh"
    username   = jwt_payload["sub"]

    # Solo bloqueamos los refresh tokens
    if token_type == "refresh":
        user = users_collection.find_one(
            {"username": username}, {"refresh_jti": 1}
        )
        # Si no hay usuario o el jti no coincide → revocado
        return not user or user.get("refresh_jti") != jti

    # Para access tokens, nunca los bloqueamos aqui
    return False

# Funciones de acceso y permisos de visualizacion
def get_accesos_usuario(users_collection,  username):
    """
   # Obtiene los id de los centros a los cuales tiene permiso el usuario
   # especificado, en la coleccion indicada y los entrega como un diccionario.
   #
   #Args:
   #users_collection: coleccion en la cual se encuentran los permisos de los usuarios (DB users) .
   #username: string con nombre de usuario.
   #
   #Returns:
   #Dict {region: [{area : [id_centro]}]} donde:
   #- region es un string que representa la region a la que pertenece el centro.
   #- area es un string que representa la comuna a la que pertenece el centro.
   #- id_centro es un string que representa el identificador unico de cada centro.
    """
    # Busca el documento del usuario por su nombre de usuario
    usuario = users_collection.find_one({"username": username})

    if usuario:
        # Accede a la key  "accesos" en el documento del usuario
        permisos = usuario.get("permisos", {})
        
        # Imprime la lista de accesos
        #print(f"Accesos del usuario:\n{permisos}")        
        return permisos
    else:
        print(f"Usuario '{username}' no encontrado.")

        return {}

#*----- Metodo para validar que el usuario poseen permisos en los id_centros indicados---- #
def validacion_permisos_usuario(permisos):
    #Si existen los permisos
    if permisos:
        return True
    else:
        return False

#*----- Metodo para validar que el usuario exista y que el token de acceso sea el mismo---- #
def check_token(client, username, access_token):
    users_collection = client[user_database_name].users
    # Busca el documento del usuario por su nombre de usuario
    usuario = users_collection.find_one({"username": username})
    # Si existe el usario
    if usuario:
        # Obtener token de refresco de base de datos
        token = usuario.get("access_token", {})
        #Comparar token de base de datos con el entregado por el Front End
        if token == access_token:
            print ("Los tokens SI coinciden")
            return True
        else:
            print ("Los tokens NO coinciden")
            return False
    #Si no existe el usuario retorna None
    else:
        print(f"Usuario '{username}' no encontrado.")
        return None

def servicios_activos_scapp(ciclos_collection):
    """
   # Obtiene los id de los centros en los cuales esta operativo el servicio
   # Scapp.
   #
   #Args:
   #ciclos_collection: coleccion en la cual se encuentran el estado de los
   # servicios por centro.
   #
   #Returns:
   #Array1D (scapp_activos) donde:
   #- scapp_activos son todos los id de centros que contrataron Scapp.
    """
    
    query = ciclos_collection.find({"scapp.status": True})
    scapp_activos = []
    for res in query:
        scapp_activos.append(res["centro_id"])
    # scapp_activos = np.array(scapp_activos)
    scapp_activos = scapp_activos
    return (scapp_activos)

#*----- Metodo para obtener los id_centros con servicios de enviro activos---- #
def servicios_activos_enviro(ciclos_collection):
    """
    # Obtiene los id de los centros en los cuales esta operativo el servicio
    # Enviro.
    #
    #Args:
    #ciclos_collection: coleccion en la cual se encuentran el estado de los
    # servicios por centro.
    #
    #Returns:
    #Array1D (enviro_activos) donde:
    #- enviro_activos son todos los id de centros que contrataron Enviro.
     """
    query = ciclos_collection.find({"enviro.status": True})
    enviro_activos = []
    for res in query:
        enviro_activos.append(res["centro_id"])
    # enviro_activos = np.array(enviro_activos)
    print(f"{enviro_activos=}")
    enviro_activos = enviro_activos
    return (enviro_activos)

#*----- Metodo para obtener los id_centros con servicios de nanoox activos---- #
def servicios_activos_nanoox(ciclos_collection):
    """
    # Obtiene los id de los centros en los cuales esta operativo el servicio
    # Nanoox.
    #
    #Args:
    #ciclos_collection: coleccion en la cual se encuentran el estado de los
    # servicios por centro.
    #
    #Returns:
    #Array1D (nanoox_activos) donde:
    #- nanoox_activos son todos los id de centros que contrataron Nanoox.
     """    
    query = ciclos_collection.find({"nanoox.status": True})
    nanoox_activos = []
    for res in query:
        nanoox_activos.append(res["centro_id"])
    # nanoox_activos = np.array(nanoox_activos)
    nanoox_activos = nanoox_activos
    return (nanoox_activos)

#*----- Metodo para obtener los id_centros con servicios de pss activos---- #
def servicios_activos_pss(ciclos_collection):
    """
    # Obtiene los id de los centros en los cuales esta operativo el servicio
    # PSS.
    #
    #Args:
    #ciclos_collection: coleccion en la cual se encuentran el estado de los
    # servicios por centro.
    #
    #Returns:
    #Array1D (pss_activos) donde:
    #- pss_activos son todos los id de centros que contrataron PSS.
     """        
    query = ciclos_collection.find({"pss.status": True})
    pss_activos = []
    for res in query:
        pss_activos.append(res["centro_id"])
    # pss_activos = np.array(pss_activos)
    pss_activos = pss_activos
    return (pss_activos)

#*----- Metodo para obtener los centros formtadeados para Front End a los que tiene permiso el usuario con servicios activos---- #
def interseccion_permisos_con_servicios(diccionario_id_centros, servicios_activos):
    """
    # Realiza la operacion logica "And (&)" o la interseccion entre
    # los id de centros a los que tiene permiso el usuario y los
    # servicios activos de cada centro, retornando los centros del servicio
    # en el formato de entrada para el Front End. 
    #
    #
    #Args:
    #diccionario_id_centros: diccionario con los id de los centros a los que tiene acceso
    # el usuario.
    #servicios_activos: arreglo con los id de los centros que poseen el servicio
    # especificado activo
    #
    #Returns:
    #Dict {region: [{area : [nombre_centro]}]} donde:
    #- region es un string que representa la region a la que pertenece el centro.
    #- area es un string que representa la comuna a la que pertenece el centro.
    #- nombre_centro es un string que representa el nombre de cada centro.
     """      
    # Diccionario con permisos
    diccionario = diccionario_id_centros
    # Arreglo con servicios activos
    arreglo = servicios_activos
    
    # Obtener los valores del tercer nivel como una lista plana
    valores_tercer_nivel = [valor for subdiccionario in diccionario.values() for lista in subdiccionario.values() for valor in lista]
    
    # Calcular la interseccion
    interseccion = list(set(valores_tercer_nivel) & set(arreglo))
    
    # Verificar si la interseccion es un conjunto vacio
    if not interseccion:
        return {}  # Devolver un diccionario vacio si la interseccion es vacia
    
    # Crear un diccionario temporal para almacenar los valores actualizados
    diccionario_actualizado = {}
    
    # Actualizar el diccionario manteniendo solo los valores presentes en la interseccion
    for region, subregiones in diccionario.items():
        for subregion, destinos in subregiones.items():
            destinos_actualizados = [destino.split('-')[0] for destino in destinos if destino in interseccion]
            if destinos_actualizados:
                # Solo agregar si la lista de destinos actualizados no esta vacia
                diccionario_actualizado.setdefault(region, {})[subregion] = destinos_actualizados
    
    return diccionario_actualizado

def validacion_permisos_usuario(permisos):
    if permisos:
        return True
    else:
        return False

#!---Funciones Auxiliares Manejo de Usuarios---¡#
def crear_usuario(usuarios_collection, data_user, usuario_actual):
    username = data_user.get('username')
    password = data_user.get('password')
    cargo = data_user.get('cargo')
    correo = data_user.get('correo')
    permisos_solicitados = data_user.get('permisos')

    if not username or not password or not cargo or not correo or not permisos_solicitados:
        return jsonify({"mensaje": "Faltan campos necesarios para el registro."}), 400

    if usuarios_collection.find_one({"username": username}):
        return jsonify({"mensaje": f"El nombre de usuario {username} ya existe."}), 409

    if len(password) < 3:
        return jsonify({"mensaje": "La contraseña es demasiado corta."}), 400

    if not _check_email_format(correo):
        return jsonify({"mensaje": "Formato de correo electrónico inválido."}), 400

    hashed_password = generate_password_hash(password)
    data_user['password'] = hashed_password

    try:
        usuario_actual_info = usuarios_collection.find_one({'username': usuario_actual})
        cargo_actual = usuario_actual_info['cargo']
        permisos_actual = usuario_actual_info['permisos']

        if cargo_actual == 'Operario':
            return jsonify({"mensaje": "No tienes permiso para crear usuarios."}), 400

        # --- Nueva detección de formato simplificado ---
        permisos_procesados = permisos_solicitados
        if cargo in ["Jefe Area", "Jefe Region"]:
            if cargo == "Jefe Area" and es_formato_simplificado_jefe_area(permisos_solicitados):
                permisos_procesados = transformar_permisos_simplificados(
                    cargo, permisos_solicitados, permisos_actual
                )
            elif cargo == "Jefe Region" and es_formato_simplificado_jefe_region(permisos_solicitados):
                permisos_procesados = transformar_permisos_simplificados(
                    cargo, permisos_solicitados, permisos_actual
                )
            elif es_formato_area_sin_centros(permisos_solicitados):
                # NUEVO: Expande las áreas sin centros usando los del creador
                permisos_procesados = {}
                for region, areas in permisos_solicitados.items():
                    if region not in permisos_actual:
                        continue
                    permisos_procesados.setdefault(region, {})
                    for area, servicios in areas.items():
                        if area not in permisos_actual[region]:
                            continue
                        permisos_procesados[region][area] = {}
                        for centro in permisos_actual[region][area]:
                            permisos_procesados[region][area][centro] = servicios.copy()
            else:
                # Se asume formato detallado (centros anidados)
                permisos_procesados = permisos_solicitados

        # Validar permisos resultantes
        permisos_validados = validacion_permisos_a_otorgar(
            cargo_actual, permisos_actual, permisos_procesados)
        if permisos_validados is None:
            return jsonify({"mensaje": "Permisos inválidos para este usuario."}), 401

        data_user['permisos'] = permisos_validados

        if verificar_permisos(cargo_actual, cargo):
            usuarios_collection.insert_one(data_user)
            return jsonify({"mensaje": f"Creación de usuario {username} exitosa con permisos asignados."}), 201
        else:
            return jsonify({"mensaje": "No se puede continuar por falta de rango."}), 400

    except Exception as e:
        print(f"Error al crear usuario {username}: {e} {e.__traceback__.tb_lineno}")
        return jsonify({"mensaje": f"Error al crear usuario {username}: {str(e)}"}), 500

def es_formato_area_sin_centros(permisos):
    """
    Detecta estructuras como region -> area -> servicios (sin centros).
    Ejemplo:
    {
      'loslagos': {
        'chiloe': {
          'enviro': {'status': False},
          'nanoox': {'status': True}
        }
      }
    }
    """
    if not isinstance(permisos, dict):
        return False
    for region, areas in permisos.items():
        if not isinstance(areas, dict):
            return False
        for area, servicios in areas.items():
            if not isinstance(servicios, dict):
                return False
            # Si los valores dentro son servicios, detecta este formato
            if all(isinstance(v, dict) and "status" in v for v in servicios.values()):
                return True
    return False


def original_crear_usuario(usuarios_collection, data_user, usuario_actual):
    username = data_user.get('username')
    password = data_user.get('password')
    cargo = data_user.get('cargo')
    correo = data_user.get('correo')
    permisos_solicitados = data_user.get('permisos')
    # permisos = data_user.get('permisos')
    
    data_user['permisos'] = permisos_solicitados
    
    if not username or not password or not cargo or not correo or not permisos_solicitados:
        return jsonify({"mensaje": "Faltan campos necesarios para el registro."}), 400

    if usuarios_collection.find_one({"username": username}):
        return jsonify({"mensaje": f"El nombre de usuario {username} ya existe."}), 409

    if len(password) < 3:
        return jsonify({"mensaje": "La contrasena es demasiado corta."}), 400

    if not _check_email_format(correo):
        return jsonify({"mensaje": "Formato de correo electronico invalido."}), 400

    hashed_password = generate_password_hash(password)
    data_user['password'] = hashed_password
    
    try:
        usuario_actual_info = usuarios_collection.find_one({'username': usuario_actual})
        cargo_actual = usuario_actual_info['cargo']
        permisos_actual = usuario_actual_info['permisos']

        if cargo_actual == 'Operario':
            return jsonify({"mensaje": "No tienes permiso para crear usuarios."}), 400

        if verificar_permisos(cargo_actual, cargo):
            if cargo in ["Jefe Region", "Jefe Area"]:
                permisos_transformados = transformar_permisos_simplificados(cargo, permisos_solicitados, permisos_actual)
                permisos_validados = validacion_permisos_a_otorgar(cargo_actual, permisos_actual, permisos_transformados)
                if permisos_validados is not None:
                    data_user['permisos'] = permisos_validados
                else:
                    return jsonify({"mensaje": "Permisos invalidos para este usuario."}), 401
            else:
                permisos_validados = validacion_permisos_a_otorgar(cargo_actual, permisos_actual, permisos_solicitados)
                if permisos_validados is not None:
                    data_user['permisos'] = permisos_validados
                else:
                    return jsonify({"mensaje": "Permisos invalidos para este usuario."}), 401

            usuarios_collection.insert_one(data_user)
            return jsonify({"mensaje": f"Creacion de usuario {username} exitosa con permisos {data_user['permisos']}."}), 201

        else:
            return jsonify({"mensaje": "No se puede continuar por falta de rango."}), 400
    except Exception as e:
        print(f"Error al crear usuario {username}: {e} {e.__traceback__.tb_lineno}")
        return jsonify({"mensaje": f"Error al crear usuario {username}: {str(e)}"}), 500

def verificar_permisos(cargo_usuario_existente, cargo_usuario_nuevo):
    #Comprueba si el usuario actual puede crear el cargo solicitado
    cargos_jerarquicos = ['Superusuario', 'Gerencia', 'Jefe Region', 'Jefe Area', 'Jefe Centro', 'Operario']
    if cargo_usuario_existente not in cargos_jerarquicos:
        raise ValueError("El cargo del usuario existente no es valido.")
    
    if cargo_usuario_nuevo not in cargos_jerarquicos:
        raise ValueError("El cargo del usuario nuevo no es valido.")
    
    indice_usuario_existente = cargos_jerarquicos.index(cargo_usuario_existente)
    indice_usuario_nuevo = cargos_jerarquicos.index(cargo_usuario_nuevo)
    
    if indice_usuario_nuevo > indice_usuario_existente:
    # if indice_usuario_existente  > indice_usuario_nuevo:
        print("El usuario tiene permiso para continuar.")
        return True
    else:
        print("El usuario no tiene permiso para continuar 1.")
        return False

def transformar_permisos_simplificados(cargo, permisos_simplificados, permisos_usuario_actual):
    nuevos_permisos = {}
    try:
        print(f"{permisos_simplificados.items()=}")
        if cargo == "Jefe Region":
            for region, servicios in permisos_simplificados.items():
                if region in permisos_usuario_actual:
                    nuevos_permisos[region] = {}
                    for area, centros in permisos_usuario_actual[region].items():
                        nuevos_permisos[region][area] = {}
                        for centro in centros:
                            nuevos_permisos[region][area][centro] = servicios.copy()
    
        elif cargo == "Jefe Area":
            for region_area, servicios in permisos_simplificados.items():
                region, area = region_area.split("-")
                if region in permisos_usuario_actual and area in permisos_usuario_actual[region]:
                    nuevos_permisos.setdefault(region, {})[area] = {}
                    for centro in permisos_usuario_actual[region][area]:
                        nuevos_permisos[region][area][centro] = servicios.copy()
    except Exception as e:
        print(f"Error al transformar_permisos_simplificados: {e} {e.__traceback__.tb_lineno}")
    return nuevos_permisos
  
def verificar_permisos_de_edicion(cargo_usuario_existente, cargo_usuario_a_editar):
    #Comprueba si el usuario actual puede crear el cargo solicitado
    cargos_jerarquicos = ['Superusuario', 'Gerencia', 'Jefe Region', 'Jefe Area', 'Jefe Centro', 'Operario']
    if cargo_usuario_existente not in cargos_jerarquicos:
        raise ValueError("El cargo del usuario existente no es valido.")
    
    if cargo_usuario_a_editar not in cargos_jerarquicos:
        raise ValueError("El cargo del usuario nuevo no es valido.")
    
    indice_usuario_existente = cargos_jerarquicos.index(cargo_usuario_existente)
    indice_usuario_nuevo = cargos_jerarquicos.index(cargo_usuario_a_editar)
    # print(f"{indice_usuario_existente=}")
    # print(f"{indice_usuario_nuevo=}")
    
    if indice_usuario_nuevo > indice_usuario_existente :
        print("El usuario tiene permiso para continuar.")
        return True
    else:
        print("El usuario no tiene permiso para continuar.")
        return False


def permisos_comunes_region(permisos_actual, permisos_x_crear):
    permisos_comunes = {
        region: {
            area: {
                key: {
                    subkey: permisos_x_crear[region][area][key].get(subkey, permisos_actual[region][area][key].get(subkey))
                    for subkey in permisos_actual[region][area][key]
                }
                for key in permisos_actual[region][area]
                if key in permisos_x_crear[region][area]
            }
            for area in permisos_x_crear[region]
            if area in permisos_actual[region]
        }
        for region in permisos_x_crear
        if region in permisos_actual
    }
    return permisos_comunes

def convertir_diccionario(diccionario):
    if not isinstance(diccionario, dict):
        return diccionario
    
    resultado = {}
    for clave, valor in diccionario.items():
        if isinstance(valor, bool):
            resultado[clave] = {'status': valor}
        else:
            resultado[clave] = convertir_diccionario(valor)
    return resultado

def centros_comunes(permisos_actual, permisos_x_crear):
    valores_comunes = {
        region: {
            area: [centro for centro in permisos_actual[region][area] if centro in permisos_x_crear[region][area]]
            for area in permisos_x_crear[region]
            if area in permisos_actual[region] and area in permisos_x_crear[region]
        }
        for region in permisos_x_crear
        if region in permisos_actual and region in permisos_x_crear
    }
    return valores_comunes


def validacion_permisos_a_otorgar(cargo_usuario_actual, permisos_actual, permisos_x_crear):
    #Valida los centros que puede otorgarse permiso
    return verificar_nivel_vacio(permisos_comunes_region(permisos_actual,permisos_x_crear))


    
def verificar_nivel_vacio(diccionario):
    def verificar(diccionario, nivel):
        # Si hemos alcanzado el cuarto nivel, no profundizamos mas
        if nivel > 3:
            return diccionario

        for clave, valor in diccionario.items():
            if isinstance(valor, dict):
                if nivel < 3:  # Solo profundizamos si estamos en el nivel 1 o 2
                    resultado = verificar(valor, nivel + 1)  # Recursivamente verificar cada nivel
                    if resultado is None:  # Si encuentra un nivel vacio, devolver None
                        return None
                    elif resultado == {}:
                        return None
                elif nivel == 3 and not valor:  # Si estamos en el tercer nivel y el diccionario esta vacio
                    return None
            elif not valor:  # Si el valor es una lista vacia o similar, devolver None
                return None
        
        return diccionario  # Si no se encontro ningun nivel vacio, devolver el diccionario de entrada

    return verificar(diccionario, 1)

def _check_email_format(email):
    # Simple chequeo de formato de correo electronico, podria ser mas complejo
    return re.match(r"[^@]+@[^@]+\.[^@]+", email)

def eliminar_usuario(users_collection, username):
    """Funcion para eliminar un usuario de la base de datos.
    
    Parametros:
    - users_collection: La coleccion de MongoDB donde se almacenan los usuarios.
    - username (str): El nombre de usuario para identificar el documento a eliminar.
    
    Retorna:
    - Un codigo de estado HTTP.
    """
    # Define el filtro para encontrar al usuario especifico
    filtro = {"username": username}

    # Elimina el primer documento que cumple con el filtro
    resultado = users_collection.delete_one(filtro)

    # Verifica si se realizo la eliminacion correctamente
    if resultado.deleted_count > 0:
        print(f"Usuario '{username}' eliminado correctamente.")
        return 200
    else:
        print(f"Usuario '{username}' no encontrado o no se elimino ningun usuario.")
        return 400
        
#OLD FUNCTION
# def editar_usuarios(collection, username, json_data):
#     filtro_usuario = {"username": username}
#     nuevos_valores = filtrar_campos(json_data)
#     print(f"{json_data=}")
#     print(f"{filtro_usuario=}")
#     print(f"{nuevos_valores=}")
#     try:
#         if collection.find_one(filtro_usuario):
#                 resultado = collection.update_one(filtro_usuario, {"$set": nuevos_valores})
        
#                 if resultado.modified_count > 0:
#                     print(f"Usuario {username} actualizado exitosamente.")
#                     # return jsonify({"mensaje": f"Usuario {username} actualizado exitosamente."}), 201
#         else:
#             print(f'No se encontro ningun usuario: {username}.')
#             return jsonify({"mensaje": f"No se encontro ningun usuario: {username}."}), 500
#     except Exception as e:
#             print(f"Entramos a excepcion: {e.__traceback__.tb_lineno}: \n{e}")
#             return jsonify({"mensaje": f"Error en script: {e.__traceback__.tb_lineno}: \n{e}."}), 409

# NEW
def editar_usuarios(users_col, username, updates, editor_info=None):
    if editor_info is None:
        raise Exception("No se ha proporcionado info del editor")
    permisos_editor = editor_info['permisos']
    cargo_editor = editor_info['cargo']

    user_target = users_col.find_one({'username': username})
    if not user_target:
        raise Exception("Usuario no encontrado.")

    # Obtener el cargo actualizado, o el anterior si no se envia
    cargo_nuevo = updates.get('cargo', user_target['cargo'])
    print(f"{cargo_nuevo=}")
    permisos_nuevos = updates.get('permisos')
    print(f"in {permisos_nuevos=}")

    if permisos_nuevos is not None:
        permisos_actuales = user_target.get("permisos", {})

        # Detecta y expande solo si el formato es SIMPLIFICADO para Jefe Area/Region
        if cargo_nuevo == "Jefe Area" and es_formato_simplificado_jefe_area(permisos_nuevos):
            permisos_nuevos = transformar_permisos_simplificados(
                cargo_nuevo, permisos_nuevos, permisos_editor
            )
        elif cargo_nuevo == "Jefe Region" and es_formato_simplificado_jefe_region(permisos_nuevos) and \
                all('-' not in k for k in permisos_nuevos.keys()):
            permisos_nuevos = transformar_permisos_simplificados(
                cargo_nuevo, permisos_nuevos, permisos_editor
            )
        # Si NO es simplificado, se asume edicion parcial y solo hace merge!

        print(f"out {permisos_nuevos=}")
        nuevos_permisos_final = merge_permisos(permisos_actuales, permisos_nuevos)
        permisos_validados = validacion_permisos_a_otorgar(
            cargo_editor, permisos_editor, nuevos_permisos_final)
        if permisos_validados is None:
            raise Exception("Permisos invalidos o superiores a los del editor.")
        updates['permisos'] = permisos_validados
    
    if "password" in updates:
        updates["password"] = generate_password_hash(updates["password"])

    users_col.update_one({'username': username}, {'$set': updates})

def es_servicio_dict(d):
    # Un dict de servicio tiene como claves solo servicios (enviro, nanoox...) y sus valores dict con "status"
    if not isinstance(d, dict):
        return False
    for v in d.values():
        if not isinstance(v, dict) or "status" not in v:
            return False
    return True

def es_formato_simplificado_jefe_region(permisos_nuevos):
    # Todas las claves regiones, y sus valores SOLO dict de servicios (no dicts anidados)
    for region, servicios in permisos_nuevos.items():
        if not es_servicio_dict(servicios):
            return False
    return True

def es_formato_simplificado_jefe_area(permisos_nuevos):
    # Todas las claves region-area, y sus valores SOLO dict de servicios (no dicts anidados)
    for k, servicios in permisos_nuevos.items():
        if '-' not in k or not es_servicio_dict(servicios):
            return False
    return True

def merge_permisos(permisos_actuales, nuevos_permisos):
    """
    Merge profundo: actualiza solo los valores presentes en nuevos_permisos.
    Mantiene los demas valores del arbol de permisos original.
    """
    if not isinstance(nuevos_permisos, dict):
        return nuevos_permisos

    resultado = permisos_actuales.copy() if isinstance(permisos_actuales, dict) else {}

    for clave, nuevo_valor in nuevos_permisos.items():
        if clave in resultado:
            resultado[clave] = merge_permisos(resultado[clave], nuevo_valor)
        else:
            resultado[clave] = nuevo_valor
    return resultado

def filtrar_campos(json_data):
    campos_filtrados = {}
    for key, value in json_data.items():
#        if key != "username" and value is not None and isinstance(value, str) and value.strip() != "":
        if value is not None and (not isinstance(value, str) or value.strip() != ""):
            campos_filtrados[key] = value
    return campos_filtrados

def transformar_permisos(data):
    resultado = {}
    
    for permiso in data['permisos']:
        region = permiso['REGION'][0].lower()  # Se asume que cada lista contiene un solo elemento
        areas = permiso['AREAS']
        
        # Asegurarse de que la region ya existe en el diccionario
        if region not in resultado:
            resultado[region] = {}
        
        for area_obj in areas:
            area = area_obj['AREA'].lower()
            centros = area_obj['CENTRO']
            
            # Asegurarse de que el area ya existe en el diccionario bajo la region
            if area not in resultado[region]:
                resultado[region][area] = {}
            
            # Construir el diccionario de centros para cada area
            for centro, sistemas in centros.items():
                centro_formato = sistemas
                
                resultado[region][area][f"{centro.lower()}-{area}-{region}"] = centro_formato
    
    return resultado

def verificar_permisos_edicion(cargo_usuario_existente, cargo_usuario_db):
    #Comprueba si el usuario actual puede crear el cargo solicitado
    cargos_jerarquicos = ['Superusuario', 'Gerencia', 'Jefe Region', 'Jefe Area', 'Jefe Centro', 'Operario']
    
    indice_usuario_existente = cargos_jerarquicos.index(cargo_usuario_existente)
    indice_usuario_db = cargos_jerarquicos.index(cargo_usuario_db)
    
    if indice_usuario_db > indice_usuario_existente:
        print("El usuario tiene permiso para continuar.")
        return True
    else:
        print("El usuario no tiene permiso para continuar 2.")
        return False

def insert_log_in_user_history(collection, username, edit_object, log_type):
    timestamp = datetime.now()
    if log_type=='renew_password':
        action = f"reestablecio su contraseña"
    elif log_type=='add_user':
        action = f"creo al usuario {edit_object}"
    elif log_type=='edit_user':
        action = f"edito al usuario {edit_object}"
    elif log_type=='delete_user':
        action = f"elimino al usuario {edit_object}"
    elif log_type=='add_center':
        action = f"creo el centro {edit_object}"
    elif log_type=='edit_center':
        action = f"edito el centro {edit_object}"
    elif log_type=='delete_center':
        action = f"elimino el centro {edit_object}"
    elif log_type=='add_scapp_threshold':
        action = f"edito los umbrales de Region Scapp del centro {edit_object}"
    elif log_type=='add_center_permission':
        action = f"agregó automáticamente permiso del centro {edit_object[1]} al ususario {edit_object[0]}"
    
    log_entry = {
        'date': timestamp,
        'username': username,
        'type': log_type,
        'msg': f'El usuario {username} {action} a las {timestamp.isoformat()}'
    }
    collection.insert_one(log_entry)
    # print(log_entry['msg'])
    return

## Funciones auxiliares para reestablecer contraseña con token reset de un solo uso usando Flask-JWT-Extended
def create_reset_token(username, expires_delta):
    # Cambia temporalmente la clave para firmar
    original_key = app.config['JWT_SECRET_KEY']
    app.config['JWT_SECRET_KEY'] = app.config['JWT_RESET_SECRET_KEY']
    token = create_access_token(identity=username, expires_delta=expires_delta, additional_claims={'reset': True})
    app.config['JWT_SECRET_KEY'] = original_key
    return token


def decode_reset_token(token):
    # Cambia temporalmente la clave para verificar firma
    original_key = app.config['JWT_SECRET_KEY']
    app.config['JWT_SECRET_KEY'] = app.config['JWT_RESET_SECRET_KEY']
    decoded = decode_token(token)
    app.config['JWT_SECRET_KEY'] = original_key
    return decoded

def ciclos_insert(client, username, centros):
    operaciones = []
    user_db = client[user_database_name]
    enviro_db = client[enviro_database_name]
    ciclos_collection = user_db.ciclos
    users_historial_collection = user_db.historial_users
    estado_centros_collection = user_db.estados

    centros_insertados = []
    #centros_existentes = []
    print(f'centros_recibidos: {centros}')
    for info_centro in centros:
        print(f"{info_centro=}")
        comprobacion = ciclos_collection.find_one({"centro_id": info_centro['centro_id']})
        try:
            dt = datetime.now()
            # info_centro['gps_coordinates']['manual']['timestamp'] = dt
            
            # info_centro['gps_coordinates'].setdefault('auto', {})
            # info_centro['gps_coordinates']['auto']['lat'] = None
            # info_centro['gps_coordinates']['auto']['lon'] = None

            # --- CAMBIOS MÍNIMOS AQUÍ ---
            # Asegurar estructura gps_coordinates y subdicts
            info_centro.setdefault('gps_coordinates', {})
            info_centro['gps_coordinates'].setdefault('manual', {})
            info_centro['gps_coordinates'].setdefault('auto', {})

            active_mode = (info_centro['gps_coordinates'].get('active') or '').strip().lower()

            if active_mode == 'auto':
                # Si es auto: manual debe existir pero con valores None
                info_centro['gps_coordinates']['manual']['lat'] = None
                info_centro['gps_coordinates']['manual']['lon'] = None
                info_centro['gps_coordinates']['manual']['timestamp'] = None
            else:
                # Si no es auto: se mantiene el comportamiento original (poner timestamp)
                info_centro['gps_coordinates']['manual']['timestamp'] = dt
                # (opcional defensivo) aseguramos llaves, sin cambiar la lógica original
                info_centro['gps_coordinates']['manual'].setdefault('lat', None)
                info_centro['gps_coordinates']['manual'].setdefault('lon', None)

            # En ambos casos, auto.lat/lon deben existir (None por defecto)
            info_centro['gps_coordinates']['auto'].setdefault('lat', None)
            info_centro['gps_coordinates']['auto'].setdefault('lon', None)
            # --- FIN CAMBIOS MÍNIMOS ---   

        except Exception as e:
            print(f"No se ha convertido timestamp log de centro por error en linea {e.__traceback__.tb_lineno}: \n{e}")
            return jsonify({"mensaje": "No se logro realizar la operacion"}), 500
        if comprobacion == None:
            # Lista de operaciones bulk_write
            operaciones.append(InsertOne(info_centro))
            centros_insertados.append(info_centro["centro_id"])

        elif comprobacion:
            # centros_existentes.append(centro['centro_id'])
            print(f"El centro {comprobacion['centro_id']} ya esta registrado \n")
    # Insertar documentos solo si no existen valores para la llave centro_id

    try:
        resultado = ciclos_collection.bulk_write(operaciones, ordered=False, bypass_document_validation=True)
        print(f'Insertados: {resultado.inserted_count} documentos')
        if len(centros_insertados)>0:
            print(f"Se cumple {len(centros_insertados)}>0")
            for centro_creado in centros_insertados:
                crear_estado_centro_default(estado_centros_collection, centro_creado)
                insert_log_in_user_history(users_historial_collection, username, centro_creado, 'add_center')
        # verificacion_status_servicios_por_centro
        return jsonify({"mensaje": f" Exito en crear centros! "}), 200
    except Exception as e:
        print(f'Warning: {e} ̣̣̣ {e.__traceback__.tb_lineno}')
        return jsonify({"mensaje": "No se logro realizar la operacion"}), 500
    

def centros_en_db(coleccion_ciclos):
    centros = []
    for dato in coleccion_ciclos.find():
        centros.append(dato["centro_id"])
    return centros

def guardar_permiso(users_collection, region, area, centro):
    """
    Actualiza los permisos de un usuario en MongoDB sin sobrescribir datos existentes.
    
    Parameters:
    - region: Nombre de la region.
    - area: Nombre del area.
    - centro: Nombre del centro.
    """
    
    permiso_key = f'{centro}-{area}-{region}'
    
    permiso = {
        permiso_key: {
            "enviro": {
                "status": True
            },
            "nanoox": {
                "status": True
            },
            "scapp": {
                "status": True
            },
            "pss": {
                "status": True
            }
        }
    }
    
    # Crear una pipeline de actualizaciones
    updates = [
        # Primero, verificamos si el usuario ya tiene permisos en esa region y area
        UpdateOne(
            {'username': "itg", f'permisos.{region}.{area}.{permiso_key}': {'$exists': False}},
            {'$set': {f'permisos.{region}.{area}.{permiso_key}': permiso[permiso_key]}},
            upsert=False  # Insertar si no existe el campo
        ),
        UpdateOne(
            {'username': "itg7", f'permisos.{region}.{area}.{permiso_key}': {'$exists': False}},
            {'$set': {f'permisos.{region}.{area}.{permiso_key}': permiso[permiso_key]}},
            upsert=False  # Insertar si no existe el campo
        ),
    ]

    # Ejecutar las actualizaciones
    result = users_collection.bulk_write(updates)
    
    if result.modified_count > 0 or result.upserted_count > 0:
        return True
    else:
        return False

def permisos_superusuario(user_db):
    ciclos_collection = user_db.ciclos
    users_collection = user_db.users 
    for centro in centros_en_db(ciclos_collection):
        centro, area, region = centro.split('-')
        guardar_permiso(users_collection, region, area, centro)
    return print('Todos los permisos de superusuario actualizados')

def actualizar_permisos_jefes(data_centros, user_db):
    """
    Procesa data_centros (lista) y agrega automáticamente permisos a jefaturas.
    - Jefe Area: requiere permisos.<region>.<area>
      Copia template desde el primer centro en esa area.
    - Jefe Region: requiere permisos.<region>
      Copia template primero desde esa area; si no hay, desde cualquier otra area de la region.
    Si no hay template disponible, usa fallback desde el input (enviro/nanoox/scapp/pss con init/fin).
    """

    user_collection = user_db.users
    users_historial_collection = user_db.historial_users

    if not data_centros or not isinstance(data_centros, list):
        print("Advertencia: data_centros está vacío o no es una lista.")
        return

    count_total_updates = 0

    for item in data_centros:
        try:
            centro_id_raw = item.get("centro_id")
            if not centro_id_raw or not isinstance(centro_id_raw, str):
                continue

            centro_id = centro_id_raw.strip().lower()

            # Parse desde la derecha: CENTRO-AREA-REGION (CENTRO puede tener guiones)
            partes = centro_id.split("-")
            if len(partes) < 3:
                print(f"Error formato ID insuficiente: {centro_id}")
                continue

            region = partes[-1]
            area = partes[-2]

            # Validación básica para paths Mongo
            if any("." in x or "$" in x for x in (centro_id, region, area)):
                print(f"Error: centro_id/area/region contienen caracteres inválidos ('.' o '$'): {centro_id}")
                continue

            # Fallback desde el input actual (init/fin)
            fallback_servicios = {}
            for k in ("enviro", "nanoox", "scapp", "pss"):
                v = item.get(k)
                if isinstance(v, dict):
                    fallback_servicios[k] = {"init": v.get("init"), "fin": v.get("fin")}
                else:
                    fallback_servicios[k] = {"init": None, "fin": None}

            mongo_path = f"permisos.{region}.{area}.{centro_id}"

            # Query correcta: distingue Jefe Area y Jefe Region
            query = {
                "$or": [
                    {"cargo": "Jefe Area",  f"permisos.{region}.{area}": {"$exists": True}},
                    {"cargo": "Jefe Region", f"permisos.{region}": {"$exists": True}},
                ]
            }

            # Proyección mínima (rápida y sin “slicing raro”)
            projection = {"username": 1,"cargo": 1, f"permisos.{region}": 1}

            users_cursor = user_collection.find(query, projection)

            bulk_operations = []
            logs_queue = [] # Cola temporal para guardar a quién debemos loguear

            for user in users_cursor:
                cargo = user.get("cargo")
                username = user.get("username", "Desconocido")
                permisos = user.get("permisos") or {}
                permisos_region = permisos.get(region)

                if not isinstance(permisos_region, dict):
                    continue

                # Evitar trabajo si ya existe el centro (si existe, saltar)
                permisos_area_actual = permisos_region.get(area)
                if isinstance(permisos_area_actual, dict) and centro_id in permisos_area_actual:
                    continue

                # Elegir template según cargo
                template = None

                if cargo == "Jefe Area":
                    # Debe existir esa área (la query ya lo filtró)
                    if isinstance(permisos_area_actual, dict) and len(permisos_area_actual) > 0:
                        # Primer centro existente como template
                        primer_centro = next(iter(permisos_area_actual))
                        if isinstance(permisos_area_actual.get(primer_centro), dict):
                            template = permisos_area_actual[primer_centro]

                elif cargo == "Jefe Region":
                    # 1) intentar template en misma área
                    if isinstance(permisos_area_actual, dict) and len(permisos_area_actual) > 0:
                        primer_centro = next(iter(permisos_area_actual))
                        if isinstance(permisos_area_actual.get(primer_centro), dict):
                            template = permisos_area_actual[primer_centro]
                    else:
                        # 2) buscar template en cualquier otra área de la región
                        for _, area_dict in permisos_region.items():
                            if not isinstance(area_dict, dict) or len(area_dict) == 0:
                                continue
                            primer_centro = next(iter(area_dict))
                            if isinstance(area_dict.get(primer_centro), dict):
                                template = area_dict[primer_centro]
                                break

                # Si no hay template, usar fallback del input
                if template is None:
                    template = fallback_servicios

                # Filtro anti-race / idempotente real (solo setear si no existe aún)
                filtro_update = {
                    "_id": user["_id"],
                    mongo_path: {"$exists": False},
                }

                bulk_operations.append(
                    UpdateOne(
                        filtro_update,
                        {"$set": {mongo_path: template}}
                    )
                )

                # Preparar Log: Guardamos info para generar el log si el update funciona
                logs_queue.append({
                    "target_user": username,
                    "centro": centro_id
                })

            if bulk_operations:
                result = user_collection.bulk_write(bulk_operations, ordered=False)
                updates_local = result.modified_count

                if updates_local > 0:
                    for entry in logs_queue:
                        insert_log_in_user_history(
                            collection=users_historial_collection,
                            username="$System", # El admin que ejecuta
                            edit_object=[entry['target_user'], entry['centro']],
                            log_type="add_center_permission"
                        )

            else:
                updates_local = 0

            count_total_updates += updates_local
            print(f"Centro '{centro_id}': Se actualizaron {updates_local} usuarios (Bulk).")

        except Exception as e:
            lineno = getattr(getattr(e, "__traceback__", None), "tb_lineno", "?")
            print(f"Error procesando centro {item.get('centro_id', 'desc')}: en línea {lineno}:\n{e}")

    print(f"=== Proceso Finalizado. Total usuarios modificados: {count_total_updates} ===")


def filtrar_valores(datos):
    valores_actualizados = {}
    for key, subdict in datos.items():
        for subkey, value in subdict.items():
            if value not in ('', None):
                if key not in valores_actualizados:
                    valores_actualizados[key] = {}
                valores_actualizados[key][subkey] = value
    return valores_actualizados

def actualizar_documento(ciclos_collection, filtro, datos):
    # Filtrar los valores del diccionario
    valores_actualizados = filtrar_valores(datos)
    print(f"{valores_actualizados=}")
     # añadimos timestamp UTC si vienen lat y lon bajo gps_coordinates, 
    gps = valores_actualizados.get('gps_coordinates')
    if gps:
        gps = gps["manual"]
    print(f"{gps=}")
    if gps is not None and 'lat' in gps and 'lon' in gps:
        # Genera el timestamp con zona UTC
        gps['timestamp'] = datetime.now() #timezone.utc
        
    if not valores_actualizados:
        print('No hay valores para actualizar.')
        return jsonify({"mensaje": "El centro no existe"}), 500

    # Construir la consulta de actualizacion
    update_query = {f'{key}.{subkey}': value
                    for key, subdict in valores_actualizados.items()
                    for subkey, value in subdict.items()}

    # Actualizar el documento en MongoDB
    result = ciclos_collection.update_one(
        filtro,
        {'$set': update_query}
    )
    if result.modified_count > 0:
        print('Documento actualizado exitosamente.')
        # verificacion_status_servicios_por_centro()
    else:
        print('No se encontro el documento o no se realizaron cambios.')
    # return

def obtener_lista_centros(centros_usuario):
    lista_centros=[]
    for region in centros_usuario:
        for area in centros_usuario[region]:
            for centro in centros_usuario[region][area]:
                lista_centros.append(centro)
    return lista_centros

def eliminar_centro_de_permisos(users_collection, centro_id):
    centro_split = centro_id.split('-')
    query = users_collection.find({}, {'_id': 0, 'username': 1})
    usuarios = [doc['username'] for doc in query if 'username' in doc]
    for usuario in usuarios:
        permiso_centro = users_collection.find_one({'username':usuario})['permisos']
        # print(f"{permiso_centro=}")
        try:
            if permiso_centro:
                permiso_centro = users_collection.find_one({'username':usuario})['permisos'][centro_split[2]][centro_split[1]][centro_id]
                print(f"{usuario}, {permiso_centro=}")
                update_result = users_collection.update_one(
                    {'username':usuario},
                    {"$unset": {f"permisos.{centro_split[2]}.{centro_split[1]}.{centro_id}": ""}}
                )
                if update_result.modified_count > 0:
                    print(f"Se ha eliminado el centro {centro_id} para el usuario {usuario}.")
                else:
                    print(f"No se encontro el centro {centro_id} para el usuario {usuario} o ya fue eliminado.")
        except:
            print(f"El usuario {usuario} no posee permisos para el centro {centro_id}")
            continue
    return 

def eliminar_estados_centro(estado_centros_collection, centro_id):
    try:
        query = estado_centros_collection.find_one({"centro_id":centro_id})
        if query:
            delete = estado_centros_collection.delete_one({"centro_id":centro_id})
            if delete.deleted_count > 0:
                print(f"Se han eliminado los estados del centro {centro_id}")
    except Exception as e:
        print(f"No se han podido eliminar los estado del centro {centro_id} por error en linea {e.__traceback__.tb_lineno}: \n{e}")
    return

def crear_umbral_default(enviro_db, centro_id, var):
    col_enviro_umbrales = enviro_db.umbrales
    bulk_operations=[]
    operation = InsertOne(
    {
      "fecha": datetime.now().strftime("%Y-%m-%d"),
      "centro_id": centro_id,
      "variable": var,
      "status": False,
      "tipo": "simple",
      "a": None,
      "b": None,
      "c": None,
      "d": None,
      "enviado": None
    })
    bulk_operations.append(operation)
    try:
        if bulk_operations:
            col_enviro_umbrales.bulk_write(bulk_operations)
            print(f'Umbral de {var} creado')
        else:
            print(f'No hay datos de umbral de {var} para guardar')
    except Exception as e:
        print(f'Error creando datos de umbrales de {var} para {centro_id} en linea {e.__traceback__.tb_lineno}: \n{e}')

def umbrales_default(enviro_db, centro_id):
    variables = ['Oxigeno', 'Doxigeno', 'Salinidad', 'Temperatura']
    for var in variables:
        crear_umbral_default(enviro_db, centro_id, var)
    print(f'Se han creado los umbrales por defecto del centro {centro_id}')

def crear_estado_centro_default(estado_centros_collection, centro_id):
    print("CREANDO ESTADO CENTRO DEFAULT")
    try:
        bulk_operations = []
        operation = InsertOne(
        {
            "centro_id": centro_id,
            "enviro": "B",
            "nanoox": "B"
        })
        bulk_operations.append(operation)
    
        if bulk_operations:
            estado_centros_collection.bulk_write(bulk_operations)
            print(f'Estado de Servicios por defecto para centro {centro_id} creado')
        else:
            print(f'No hay datos para crear Estado de Servicios para centro {centro_id}')
    except Exception as e:
        print(f'Error creando Estado de Servicios por defecto para {centro_id} en linea {e.__traceback__.tb_lineno}: \n{e}')
    return

def get_last_measure_sensor_date(modulos):
    if modulos:
        last_msg_date=[]
        for id in modulos:
            # print(id)
            for j in range(len(modulos[id]["cans"])):
                for bomba in modulos[id]["cans"][j]['bombas']:
                    last_msg_date.append(bomba['hora'])
        # print(lista_de_horas)
        last_msg_date.sort(key=lambda last_msg_date: datetime.strptime(last_msg_date, '%H:%M:%S'), reverse=True)
        # print(lista_de_horas)
        final_date = str(datetime.now().strftime("%Y-%m-%d") + ' ' + last_msg_date[0])
        # print(f"{final_date=}")
    else:
        final_date = ''
    return final_date




#!---FUNCIONES AMBIENTAL---!
#!----FUNCIONES NANOOX----!

#------Declaracion de rutas HTTP basado en Flask-------#

#!---FRONT-END---!

# Ruta para servir los archivos estaticos de React
@app.route('/', defaults={'path': ''})

@app.route('/<path:path>')
def serve(path):
    user_agent_str = request.headers.get('User-Agent', '')
    static_folder = 'build'  # carpeta por defecto para PCs

    # Cambiar carpeta estatica si es un dispositivo movil
    if is_mobile(user_agent_str):
        static_folder = 'build'# 'build_FE_mobile'

    # Comprobar si el archivo solicitado existe
    if path != "" and os.path.exists(os.path.join(static_folder, path)):
        return send_from_directory(static_folder, path)
    else:
        return send_from_directory(static_folder, 'index.html')
    
#!---BACK-END---!
    
# ? Test de comunicacion FE
@app.route('/check_server_status', methods=['HEAD'])
def check_server_status():
    return ('', 200)

# ? Server ping - usado por cliente backend
@app.route( '/ping', methods=['GET'] )
def ping():

    print("pong")
    return jsonify({"message": "pong"})

#Ingreso y refresco de token
@app.route('/login', methods=['POST'])
def login():
    """ 
    # login: inicio de sesion, recibe unicamente la informacion a la que tiene acceso
    # recibe  json en el formato:
    #   {
    #       "username": "usuario",
    #       password": "password"
    #   }   
    """
    client = get_db()
    users_collection = client[user_database_name].users

    # Validar que la solicitud contenga un JSON
    if not request.is_json:
        return jsonify({"mensaje": "Falta JSON en la solicitud"}), 400
    
    username = request.json.get("username", None)  #obtiene username del json enviado en la peticion http
    password = request.json.get("password", None)  #obtiene username del json enviado en la peticion http
    remember = request.json.get("remember", False)  # booleano
    print(f"json: {request.json}")
    print(f"remember: {remember}")
    # Validar que el username y password esten presentes en base a las dos lineas anteriores
    if not username or not password:
        return jsonify({"mensaje": "Falta username o password"}), 400
    

    # Busca el documento del usuario por su nombre de usuario
    user = users_collection.find_one({"username": username})
    print(user)          

    # Verificar que el usuario existe y que la contrasena es correcta
    if not user or not check_password_hash(user.get('password'), password):
        return jsonify({"mensaje": "Credenciales invalidas"}), 401
    
    # Crear los tokens de acceso y actualizacion
    access_token = create_access_token(identity=username)
    # refresh corto o largo segun “remember me” de usuario en front
    rt_expires = timedelta(days=7) if remember else timedelta(days=2)
    refresh_token = create_refresh_token(identity=username,  expires_delta=rt_expires)

    # Extraer JTI del refresh token recien creado
    decoded_rt = decode_token(refresh_token)
    jti_refresh = decoded_rt["jti"]
    
   # Guardar el access, refresh token y JTI en la base de datos
    users_collection.update_many({"username": username}, {'$set': {'access_token': access_token, 'refresh_token': refresh_token, 'refresh_jti': jti_refresh}})   

        
    print(f'usuario {username} logeado correctamente')

     # Construir la respuesta JSON con los mismos campos
    resp = jsonify({
        "username":      username,
        "cargo":         user.get("cargo"),
        # "access_token":  access_token,
        # "refresh_token": refresh_token
    })

    # Inyectar tokens en cookies segun tu configuracion de Flask-JWT-Extended
    set_access_cookies(resp, access_token)
    set_refresh_cookies(resp, refresh_token, max_age=rt_expires.total_seconds())
    print(f"login {resp =}")
    return resp, 200

## MANEJO Y REFRESCO DE TOKENS ##
@app.route('/protected', methods=['GET'])
@jwt_required()
def protected():
    """
    Ruta protegida que requiere un token de acceso (access token) valido en el encabezado Authorization.

    Esta funcion es util para verificar que un usuario ha iniciado sesion y tiene autorizacion para acceder a recursos protegidos.
    El token debe incluirse en el header HTTP como: Authorization: Bearer <access_token>

    Retorna:
        JSON con el nombre de usuario (identity) que esta actualmente autenticado.
    """
    current_user = get_jwt_identity()
    return jsonify(logged_in_as=current_user), 200

@app.route('/refresh', methods=['POST'])
@jwt_required(refresh=True)
def refresh():
    """
    Ruta para renovar el token de acceso (access token) utilizando un token de actualizacion (refresh token).

    Esta funcion permite al usuario mantener la sesion activa sin volver a iniciar sesion, 
    siempre que el refresh token aun sea valido.
    
    El refresh token debe incluirse en el encabezado HTTP como: Authorization: Bearer <refresh_token>

    Retorna:
        Un nuevo token de acceso (access_token) en formato JSON.
    """
    current_user = get_jwt_identity()
    new_access_token = create_access_token(identity=current_user)
    resp = jsonify(msg="Token renovado")
    set_access_cookies(resp, new_access_token)
    # return jsonify(access_token=new_access_token), 200

    # response = make_response(jsonify(msg="Token renovado"))
    # response.set_cookie("access_token", new_access_token,
    #                     httponly=True, secure=True, samesite="Strict", max_age=15*60)
    return resp, 200

# ---------------------------------------------------
#   LOGOUT → revoca el refresh token y limpia cookies
# ---------------------------------------------------
@app.route('/logout', methods=['POST'])
@jwt_required(refresh=True)
def logout():
    username = get_jwt_identity()
    client = get_db()
    users_collection = client[user_database_name].users
    # Elimina el JTI de la BD → revoca cualquier sesion
    users_collection.update_one(
        {"username": username},
        {"$unset": {"refresh_jti": ""}}
    )
    # Borra cookies en el cliente
    resp = jsonify(msg="Logout exitoso")
    resp.delete_cookie(app.config["JWT_ACCESS_COOKIE_NAME"])
    resp.delete_cookie(app.config["JWT_REFRESH_COOKIE_NAME"])
    print(f"Cerrando sesion {resp=}")
    return resp, 200

# ! -- Recepcion de datos desde clientes ( Centros con estaciones ) -- #

@app.route('/<SERVICE>/receive_client_data/<REGION>/<AREA>/<CENTRO>', methods=['POST'])
def receive_client_data( SERVICE, REGION, AREA, CENTRO ):
    """# all_sensors_data['data']: [
            {
                'Nombre' 
                "fecha"
                "hora"
                "dif_last"
                "device_id"
                "dev_eui"
                "f_port"
                "snr"
                "rssi"
                "channel_rssi"
                "spreading_factor"
                "f_cnt"
                "uplink_message"
                "timeStamp"
                "batteryPercent"
                "Port 2"
                "formatted_time"
                "Port 3"
                "batteryVoltageMv"
                "flags"
                prof_i sensores data for i={0,1,2,3}
            } , 
            {...} , ...
        ]
         """
    # SERVICE = "enviro"
    # CENTRO  = "Chalihue"
    # REGION  = "Chiloe"
    # AREA    = "Loslagos"
    if SERVICE == "ENVIRO":
        try:
                client = get_db()
                enviro_data_collection = client[enviro_database_name].data

                CENTRO  = CENTRO.replace(' ','').replace('-','').capitalize()
                REGION  = REGION.replace(' ','').replace('-','').capitalize()
                AREA    = AREA.replace(' ','').replace('-','').capitalize()

                #!_________________________________________________________________________________

                # Obtener la coleccion de MongoDB correspondiente a los datos de sensores del cliente
                collection = enviro_data_collection
            
                # Obtener los datos del sensor del cuerpo de la solicitud POST
                all_sensors_data = request.json
                #pprint(f'full json recibido: {all_sensors_data}')
        
                # Verificar si se recibieron datos
                if not all_sensors_data:
                    print("Datos no recibidos")
                    return jsonify({"mensaje": "Datos no recibidos"}), 400
        
                #print(f'all_sensors_data: {all_sensors_data}')
        
                if not all_sensors_data:
                    print("Datos no recibidos")
                    return jsonify({"mensaje": "Datos no recibidos"}), 400
        
                #print(f'all_sensors_data: \ttipo: {type(all_sensors_data)} \tlen: {len(all_sensors_data)} ')

                # Definir el ID del centro:
                centroId = f'{CENTRO}-{AREA}-{REGION}'

                if isinstance(all_sensors_data, dict):
                    print("Entramos al loop de <dict>")
                    all_sensors_data = all_sensors_data.get('data')
                    #print(f'all_sensors_data_dict: {all_sensors_data}')
                
                elif isinstance(all_sensors_data, list):
                    print("Entramos al loop de <list>")
                    #print(f'all_sensors_data_list: {all_sensors_data}')
                else:
                    print("no se esta cumpliendo con la condicion de clase")
            
                # Guardar elementos recibidos en BD
                _save_client_data( collection, all_sensors_data, centroId ) 

                # Revisar si metadata esta actualizada para este centro
                check_metadata(REGION, AREA, CENTRO)
            
                print(f'Guardado datos desde centro {centroId}')
                print("Datos recibidos exitosamente")
                return jsonify({"mensaje": "Datos recibidos exitosamente"}), 200

    
        except Exception as e:
            print(f'Error en receive_client_data linea {e.__traceback__.tb_lineno}: \n{e} ')
            return jsonify({"mensaje": f"Error al procesar los datos: {str(e)}"}), 500
    elif SERVICE == "nanoox":
        print(f'Datos Nanoox se envian de otra forma')
        return jsonify({"mensaje": "Datos Nanoox se envian de otra forma"}), 500
    elif SERVICE == "scapp":
        print(f'Datos Scapp se envian de otra forma')
        return jsonify({"mensaje": "Datos Scapp se envian de otra forma"}), 500
    elif SERVICE == "pss":
        print(f'Datos PSS se envian de otra forma')
        return jsonify({"mensaje": "Datos PSS se envian de otra forma"}), 500

#*----- Metodo para  obtener los permisos de los centros para un usuario---- #
@app.route('/<SERVICE>/get_centers', methods=['POST'])
@jwt_required()
def get_permisos_usuarios(SERVICE):
    usuario = get_jwt_identity()
    # print(f"get_centers {usuario=}")
    # print(f"data_session_user {request.json}")
    client = get_db()
    users_collection = client[user_database_name].users
    ciclos_collection = client[user_database_name].ciclos

    permisos = get_accesos_usuario(users_collection,  usuario)
    validacion = validacion_permisos_usuario(permisos)
    if validacion:
        if SERVICE == "enviro":
            respuesta = interseccion_permisos_con_servicios(permisos,servicios_activos_enviro(ciclos_collection))
        elif SERVICE == "nanoox":
            respuesta = interseccion_permisos_con_servicios(permisos,servicios_activos_nanoox(ciclos_collection))
        elif SERVICE == "scapp":
            respuesta = interseccion_permisos_con_servicios(permisos,servicios_activos_scapp(ciclos_collection))
        elif SERVICE == "pss":
            respuesta = interseccion_permisos_con_servicios(permisos,servicios_activos_pss(ciclos_collection))
        # print(f'{respuesta=}')
        return jsonify({"IDs": respuesta}), 201
    else:
        return jsonify({"mensaje": "El usuario no posee permisos de visualizacion"}), 401


# @app.route('/<SERVICE>/get_data/<REGION>/<AREA>/<CENTRO>', methods=['GET'])
# def get_data(SERVICE, REGION, AREA, CENTRO):
#     logger.debug("EN GET DATA")
#     if SERVICE == "enviro":
#         """Devuelve los datos mas recientes de todas las estaciones."""
#         try:
#             client = get_db()
#             enviro_data_collection = client[enviro_database_name].data

#             REGION  = REGION.replace(' ','').replace('-','').capitalize()
#             AREA    = AREA.replace(' ','').replace('-','').capitalize()
#             CENTRO  = CENTRO.replace(' ','').replace('-','').capitalize()
            
#             # Obtener la coleccion de MongoDB correspondiente a los datos de sensores del cliente
#             collection = enviro_data_collection
    
#             # Obtener la fecha actual en el formato adecuado
#             today = datetime.now().strftime("%Y-%m-%d")
    
#             # Construir la ID del centro
#             centro_id = f"{CENTRO}-{AREA}-{REGION}"
    
#             # Buscar el documento correspondiente al centro y la fecha de hoy
#             document = collection.find_one({'centroId': centro_id, 'fecha': today})
    
#             if not document:
#                 print(f'No hay datos para el centro {CENTRO} en el dia solicitado')
#                 return jsonify({"mensaje": f"Error: no hay datos del centro"}), 500
            
#             # Obtener los datos de todas las estaciones del dia de hoy para el centro solicitado
#             latest_data = _get_all_station_data(document, all_day_data=False)
    
#             # Verificar que hayan datos:
#             if latest_data == {}: 
#                 return jsonify({"mensaje": f"Datos solicitados no encontrados en el servidor"}), 500
                
#             sorted_array = [latest_data[k] for k in sorted(latest_data)]
            
#             dict_to_send = {
#                 'batch_data' : False,
#                 'sensor_data' : sorted_array
#             }
#             return jsonify(dict_to_send), 200
#         except Exception as e:
#             print(f'Error en get_data linea {e.__traceback__.tb_lineno}: \n{e}')
#             return jsonify({"mensaje": f"Error al obtener los datos: {str(e)}"}), 500
    
#     elif SERVICE.lower() == "nanoox":
        
#         # logger.info(f'%%%%%%%%% Previo a Solicitando datos de {REGION}/{AREA}/{CENTRO} $$$$$$$$')
#         logger.info(f"{f' Solicitando datos para {SERVICE } de  {REGION}/{AREA}/{CENTRO} ':=^60}\n")

        
#         try:
#             # Estandarizar inputs
#             client = get_db()
#             nanoox_db = client[nanoox_database_name]

#             REGION  = REGION.replace(' ','').replace('-','').capitalize()
#             AREA    = AREA.replace(' ','').replace('-','').capitalize()
#             CENTRO  = CENTRO.replace(' ','').replace('-','').capitalize()
#             # centro_id =  {REGION}-{AREA}-{CENTRO}
#             centro_id =  f"{CENTRO}-{AREA}-{REGION}"
#             centro_id_nanoox = f"{REGION}-{AREA}-{CENTRO}"
            

#             tipo_control = get_control_by_centro_id(nanoox_db, centro_id_nanoox)

#             control_version = get_control_version_by_centro_id(nanoox_db, 
#                                                             metadata_col_name =f"{CENTRO}_metadata",
#                                                             centro_id = centro_id_nanoox)
#             print(f'{CENTRO=} {control_version=}')

           
#             center_latest_data = get_latest_data_ts(nanoox_db, centro_id_nanoox)  

                              
#             # print(f"\n{' Modulos ':*^60}\n")
#             # print(modulos)
#             modulos = center_latest_data.get("modulos")
 
#             try:                
#                 hora = modulos[0]["cans"][0]["timestamp"].strftime("%Y-%m-%d %H:%M:%S")
#             except Exception as e:
#                 print(f'Advertencia en linea {e}, { e.__traceback__.tb_lineno}')
#                 hora = ' --'

#             # Verificar si se han obtenido datos para el centro
#             if not modulos: print(f"No existen datos de bombas para el centro {REGION}/{AREA}/{CENTRO}")
            
#             # Obtener historial de control de las Aviot's del centro            
#             # historial_control = get_historial_control(nanoox_db, centro_id_nanoox)
#             # print(f"{historial_control=}")
            

#             try:
#                 if not control_version:
#                     print(f"{CENTRO} debe consultar generadores antiguos")
#                     generator = get_generator(nanoox_db, centro_id_nanoox)
#                     print(f"{CENTRO} {generator=}")
#                     center_latest_data.update({"generadores":generator} )

#             except:
#                 generator = None

            
#             umbrales = get_umbrales_nanoox(nanoox_db, centro_id_nanoox, new_version = control_version)
#             # print(f"{centro_id_nanoox=}{umbrales=}")

                
#             logger.info(f'Nanoox: Enviados correctamente datos de {REGION}/{AREA}/{CENTRO} ')                        
#             return jsonify({
#                             # "modulos": modulos , 
#                             # 'generadores': generator, 
#                             **center_latest_data,

#                             # "historial_control": historial_control, 
#                             "hora": hora, 
                            
#                             'umbrales': umbrales}), 200      
#             # return jsonify({}), 200                    
#             #              
        
#         except Exception as e:
#             logger.error(f'Error en get_data linea {e.__traceback__.tb_lineno}: \n{e} ')
#             return jsonify({ "error": f"Error interno del servidor en linea {e.__traceback__.tb_lineno}: \n{e}"  }), 500
        
#     else:
#         #TODO 
#         return jsonify({ "error": f"Servicio recibido no existe  {SERVICE} : \n{e}"  }), 418

@app.route('/<SERVICE>/get_data/<REGION>/<AREA>/<CENTRO>', methods=['GET'])
def get_data(SERVICE, REGION, AREA, CENTRO):
    logger.debug("EN GET DATA")
    if SERVICE == "enviro":
        """Devuelve los datos mas recientes de todas las estaciones."""
        try:
            client = get_db()
            enviro_data_collection = client[enviro_database_name].data

            REGION  = REGION.replace(' ','').replace('-','').capitalize()
            AREA    = AREA.replace(' ','').replace('-','').capitalize()
            CENTRO  = CENTRO.replace(' ','').replace('-','').capitalize()
            
            # Obtener la coleccion de MongoDB correspondiente a los datos de sensores del cliente
            collection = enviro_data_collection
    
            # Obtener la fecha actual en el formato adecuado
            today = datetime.now().strftime("%Y-%m-%d")
    
            # Construir la ID del centro
            centro_id = f"{CENTRO}-{AREA}-{REGION}"
    
            # Buscar el documento correspondiente al centro y la fecha de hoy
            document = collection.find_one({'centroId': centro_id, 'fecha': today})
    
            if not document:
                print(f'No hay datos para el centro {CENTRO} en el dia solicitado')
                return jsonify({"mensaje": f"Error: no hay datos del centro"}), 500
            
            # Obtener los datos de todas las estaciones del dia de hoy para el centro solicitado
            latest_data = _get_all_station_data(document, all_day_data=False)
    
            # Verificar que hayan datos:
            if latest_data == {}: 
                return jsonify({"mensaje": f"Datos solicitados no encontrados en el servidor"}), 500
                
            sorted_array = [latest_data[k] for k in sorted(latest_data)]
            
            dict_to_send = {
                'batch_data' : False,
                'sensor_data' : sorted_array
            }
            return jsonify(dict_to_send), 200
        except Exception as e:
            print(f'Error en get_data linea {e.__traceback__.tb_lineno}: \n{e}')
            return jsonify({"mensaje": f"Error al obtener los datos: {str(e)}"}), 500
    
    elif SERVICE.lower() == "nanoox":

        logger.info(f"{f' Solicitando datos para {SERVICE } de  {REGION}/{AREA}/{CENTRO} ':=^60}\n")

        try:
            client = get_db()
            nanoox_db = client[nanoox_database_name]

            REGION  = REGION.replace(' ','').replace('-','').capitalize()
            AREA    = AREA.replace(' ','').replace('-','').capitalize()
            CENTRO  = CENTRO.replace(' ','').replace('-','').capitalize()

            centro_id = f"{CENTRO}-{AREA}-{REGION}"
            centro_id_nanoox = f"{REGION}-{AREA}-{CENTRO}"

            tipo_control = get_control_by_centro_id(nanoox_db, centro_id_nanoox)

            control_version = get_control_version_by_centro_id(
                nanoox_db,
                metadata_col_name=f"{CENTRO}_metadata",
                centro_id=centro_id_nanoox
            )
            print(f'{CENTRO=} {control_version=}')

            center_latest_data = get_latest_data_ts(nanoox_db, centro_id_nanoox)

            
            if not isinstance(center_latest_data, dict):
                logger.warning(
                    f"get_latest_data_ts devolvió {type(center_latest_data)} -> se envuelve como 'raw'"
                )
                center_latest_data = {"raw": center_latest_data}

            
            modulos = center_latest_data.get("modulos") or []
            if not isinstance(modulos, list):
                modulos = []

            
            hora = " --"
            try:
                ts = modulos[0]["cans"][0]["timestamp"]
                if hasattr(ts, "strftime"):
                    hora = ts.strftime("%Y-%m-%d %H:%M:%S")
            except (IndexError, KeyError, TypeError) as e:
                logger.warning(f"Advertencia obteniendo hora: {e}")

            if not modulos:
                print(f"No existen datos de bombas para el centro {REGION}/{AREA}/{CENTRO}")

            # Si es versión antigua, intenta generadores (sin romper si no hay)
            generator = None
            try:
                if not control_version:
                    print(f"{CENTRO} debe consultar generadores antiguos")
                    generator = get_generator(nanoox_db, centro_id_nanoox)
                    print(f"{CENTRO} {generator=}")
                    center_latest_data["generadores"] = generator
            except Exception as e:
                logger.warning(f"Error obteniendo generadores antiguos: {e}")

            umbrales = get_umbrales_nanoox(nanoox_db, centro_id_nanoox, new_version=control_version)

            logger.info(f'Nanoox: Enviados correctamente datos de {REGION}/{AREA}/{CENTRO} ')
            return jsonify({
                **center_latest_data,
                "hora": hora,
                "umbrales": umbrales
            }), 200

        except Exception as e:
            logger.error(f'Error en get_data linea {e.__traceback__.tb_lineno}: \n{e} ')
            return jsonify({
                "error": f"Error interno del servidor en linea {e.__traceback__.tb_lineno}: \n{e}"
            }), 500


def get_last_n_measurements(db, bomba_id, N = 360 ):
    """
    Obtiene las ultimas N mediciones de O2 y SO2 del documento correspondiente al dia de hoy.

    :param db: Instancia de la base de datos de MongoDB.
    :param bomba_id: ID de la bomba para filtrar los documentos.
    :param N: Numero de mediciones a obtener.
    :return: Diccionario con listas de valores de O2 y SO2.
    """
    # Obtener la fecha de hoy en formato AA-MM-dd
    today_date = datetime.today().strftime("%Y-%m-%d")

    try:

        nanoox_db = db        
        # Buscar el documento mas reciente con la bomba y fecha de hoy
        document = nanoox_db.sensores.find_one(
            {"bomba_id": bomba_id, "fecha": today_date},
            sort=[("_id", -1)]  # Ordenamos por _id descendente para obtener el mas reciente
        )
        

        # print(f"{bomba_id=}")
        
        if not document or "sensores" not in document:
            return {"O2": [], "SO2": []}
        


        # Extraer las mediciones O2 y SO2 de los sensores, filtrando valores None
        O2_values = [round(s["O2"],2) for s in document["sensores"] if s.get("O2") is not None]
        SO2_values = [round(s["SO2"],2) for s in document["sensores"] if s.get("SO2") is not None]
        TS = [s["last_measure_sensor"] for s in document["sensores"] if s.get("last_measure_sensor") != "Sin datos"]
        

        # Tomar solo los ultimos N valores
        return {
            "O2": O2_values[-N:][::10],
            "SO2": SO2_values[-N:][::10],
            "TS": TS[-N:][::10]
        }

    except Exception as e:
        print(f"Error: {e} {e.__traceback__.tb_lineno}")

def latest_measurements_conditioner(db, REGION,AREA,CENTRO, center_requested_data):
    # print("ESTAMOS EN LAST_MEASUREMENTS_CONDITIONER")
    data = copy.deepcopy(center_requested_data)

    try:

        for idx_mod, modulo in enumerate(center_requested_data):              #["modulos"]
            
            for idx_can,can in enumerate(center_requested_data[modulo]["cans"]):        
            
                for idx_bomba, bomba in enumerate(can.get("bombas", [])):        
                    aviot = bomba.get("ip_aviot")
                    bomba_name = bomba.get("nombre")
                    bomba_id = f"{REGION}-{AREA}-{CENTRO}-{aviot}-{modulo}-{bomba_name}"  
                    result = get_last_n_measurements(db, bomba_id= bomba_id)   
                    # print(f"{bomba_name} {result}")

                    # data["modulos"][idx_mod]["cans"][idx_can]["bombas"][idx_bomba].update({"latest_measurements": result})
                    data[modulo]["cans"][idx_can]["bombas"][idx_bomba].update({"latest_measurements": result})
    except Exception as e:
        print(f"Error: {e} {e.__traceback__.tb_lineno}")
    return data

#?-------INICIO DE RUTAS----------
@app.route('/<SERVICE>/get_latest_data/<REGION>/<AREA>/<CENTRO>', methods=['GET'])
def get_latest_data(SERVICE, REGION, AREA, CENTRO):
    # print(f'$$$$$$$$$$$$$ Solicitando datos de {REGION}/{AREA}/{CENTRO} $$$$$$$$')
    if SERVICE == "enviro":   
        """Devuelve los datos mas recientes de todas las estaciones."""
        try:
            client = get_db()
            enviro_data_collection = client[enviro_database_name].data

            REGION  = REGION.replace(' ','').replace('-','').capitalize()
            AREA    = AREA.replace(' ','').replace('-','').capitalize()
            CENTRO  = CENTRO.replace(' ','').replace('-','').capitalize()
            
            # Obtener la coleccion de MongoDB correspondiente a los datos de sensores del cliente
            collection = enviro_data_collection
    
            # Obtener la fecha actual en el formato adecuado
            today = datetime.now().strftime("%Y-%m-%d")
    
            # Construir la ID del centro
            centro_id = f"{CENTRO}-{AREA}-{REGION}"
    
            # Buscar el documento correspondiente al centro y la fecha de hoy
            document = collection.find_one({'centroId': centro_id, 'fecha': today})
    
            if not document:
                print(f'No hay datos para el centro {CENTRO} en el dia solicitado')
                return jsonify({"mensaje": f"Error: no hay datos del centro"}), 500
            
            # Obtener los datos de todas las estaciones del dia de hoy para el centro solicitado
            latest_data = _get_all_station_data(document, all_day_data=False)
    
            # Verificar que hayan datos:
            if latest_data == {}: 
                return jsonify({"mensaje": f"Datos solicitados no encontrados en el servidor"}), 500
                
            sorted_array = [latest_data[k] for k in sorted(latest_data)]
            
            dict_to_send = {
                'batch_data' : False,
                'sensor_data' : sorted_array
            }
            return jsonify(dict_to_send), 200
        except Exception as e:
            print(f'Error en get_latest_data linea {e.__traceback__.tb_lineno}: \n{e}')
            return jsonify({"mensaje": f"Error al obtener los datos: {str(e)}"}), 500
    
    elif SERVICE == "nanoox":
#        client_NAME = 'cliente'
        try:
            client = get_db()
            nanoox_db = client[nanoox_database_name]
            # Estandarizar inputs
            REGION  = REGION.replace(' ','').replace('-','').capitalize()
            AREA    = AREA.replace(' ','').replace('-','').capitalize()
            CENTRO  = CENTRO.replace(' ','').replace('-','').capitalize()
            
            # Obtener todos los IDs de CAN y Bombas e historial_control del centro
            can_ids, bomb_ids, historial_control_ids = get_center_ids( nanoox_db, REGION, AREA, CENTRO )
    
            # Diccionario de datos para enviar al front
            #Se elimino el argumento client_NAME
            modulos = _get_last_cand_and_bomb_data( nanoox_db, can_ids, bomb_ids )
            
            # Verificar si se han obtenido datos para el centro
            if not modulos: print(f"No existen datos de bombas para el centro {REGION}/{AREA}/{CENTRO}")
            
            # Obtener historial de control de las Aviot's del centro
            historial_control = {}
            
            for historial_control_id in historial_control_ids:
                aviot_id = historial_control_id.split('-')[-1] # Obtener la IP de la Aviot
                historial_control[ aviot_id ] = []
                #Se elimino el argumento salmonera
                historial_aviot = get_historial_control(nanoox_db, historial_control_id )
                
                if historial_aviot and len(historial_aviot) > 0:
                    historial_control[ aviot_id ].append(historial_aviot)
            
            formatted_modulos = []
            for modulos_data in modulos.values():
                formatted_modulos.append( modulos_data )
            print(f"{historial_control=}")
                
            print(f'Enviados correctamente datos de {REGION}/{AREA}/{CENTRO} ')
            
            return jsonify({"modulos": formatted_modulos , "historial_control": historial_control }), 200
        
        except Exception as e:
            print(f'Error en get_latest_data linea {e.__traceback__.tb_lineno}: \n{e} ')
            return jsonify({ "error": f"Error interno del servidor en linea {e.__traceback__.tb_lineno}: \n{e}"  }), 500

# Obtener datos de todo un dia para un Centro
@app.route('/<SERVICE>/get_all_day_data/<REGION>/<AREA>/<CENTRO>', methods=['GET'])
def all_day_data(SERVICE, REGION, AREA, CENTRO):
    if SERVICE == "enviro":
        """Entrega datos del ultimo dia de todas las estaciones."""
        try:
            client = get_db()
            enviro_data_collection = client[enviro_database_name].data

            REGION  = REGION.replace(' ','').replace('-','').capitalize()
            AREA    = AREA.replace(' ','').replace('-','').capitalize()
            CENTRO  = CENTRO.replace(' ','').replace('-','').capitalize()
            # Obtener la coleccion de MongoDB correspondiente a los datos de sensores del cliente
#           collection = server_db[f"{client}_sensor_data"]
            collection = enviro_data_collection
    
            # Obtener la fecha actual en el formato adecuado
            today = datetime.now().strftime("%Y-%m-%d")
    
            # Construir la ID del centro
            centro_id = f"{CENTRO}-{AREA}-{REGION}"
    
            # Buscar el documento correspondiente al centro y la fecha de hoy
            document = collection.find_one({'centroId': centro_id, 'fecha': today})
    
            if not document:
                print(f'No hay datos para el centro {CENTRO} en el dia solicitado')
                return jsonify({"mensaje": f"Error: no hay datos del centro"}), 500
            
            # Obtener los datos de todas las estaciones del dia de hoy para el centro solicitado
            all_day_data = _get_all_station_data(document, all_day_data=True)
            
            # Verificar que hayan datos:
            if all_day_data == {}: 
                return jsonify({"mensaje": f"Datos solicitados no encontrados en el servidor"}), 500
            
            sorted_array = [all_day_data[k] for k in sorted(all_day_data)]
            dict_to_send = {
                'batch_data' : True,
                'sensor_data' : sorted_array
            }
            
            return jsonify(dict_to_send), 200
        except Exception as e:
            print(f'Error en all_day_data linea {e.__traceback__.tb_lineno}: \n{e}')
            return jsonify({"mensaje": f"Error al obtener los datos: {str(e)}"}), 500

    elif SERVICE == "nanoox":
        
#        client_NAME = 'cliente'
        try:
            client = get_db()
            nanoox_db = client[nanoox_database_name]
            # Estandarizar inputs
            REGION  = REGION.replace(' ','').replace('-','').capitalize()
            AREA    = AREA.replace(' ','').replace('-','').capitalize()
            CENTRO  = CENTRO.replace(' ','').replace('-','').capitalize()
            # Obtener todos los IDs de CAN y Bombas e historial_control del centro
            can_ids, bomb_ids, historial_control_ids = get_center_ids( nanoox_db, REGION, AREA, CENTRO )
    
            # Diccionario de datos para enviar al front
            #Se elimino el argumento client_NAME
            modulos = _get_last_cand_and_bomb_data( nanoox_db, can_ids, bomb_ids )
            
            # Verificar si se han obtenido datos para el centro
            if not modulos: print(f"No existen datos de bombas para el centro {REGION}/{AREA}/{CENTRO}")
            
            # Obtener historial de control de las Aviot's del centro
            historial_control = {}
            
            for historial_control_id in historial_control_ids:
                aviot_id = historial_control_id.split('-')[-1] # Obtener la IP de la Aviot
                historial_control[ aviot_id ] = []
                #Se elimino el argumento salmonera
                historial_aviot = get_historial_control(nanoox_db, historial_control_id )
                
                if historial_aviot and len(historial_aviot) > 0:
                    historial_control[ aviot_id ].append(historial_aviot)
            
            formatted_modulos = []
            for modulos_data in modulos.values():
                formatted_modulos.append( modulos_data )

            print(f"{historial_control=}")
             
            print(f'Enviados correctamente datos de {REGION}/{AREA}/{CENTRO} ')
            
            return jsonify({"modulos": formatted_modulos , "historial_control": historial_control }), 200
        
        except Exception as e:
            print(f'Error en get_all_day_data linea {e.__traceback__.tb_lineno}: \n{e} ')
            return jsonify({ "error": f"Error interno del servidor en linea {e.__traceback__.tb_lineno}: \n{e}"  }), 500

# ? Datos de multiples dias
# Obtener datos de todas las estaciones de multiples dias
@app.route('/<SERVICE>/get_multiple_days_data/<REGION>/<AREA>/<CENTRO>', methods=['GET', 'POST'])
def multiple_days_data(SERVICE, REGION, AREA, CENTRO):
    if SERVICE == "enviro":
        """Entrega datos de multiples dias de todas las estaciones de un centro especifico."""
        try:
            client = get_db()
            enviro_data_collection = client[enviro_database_name].data

            REGION  = REGION.replace(' ','').replace('-','').capitalize()
            AREA    = AREA.replace(' ','').replace('-','').capitalize()
            CENTRO  = CENTRO.replace(' ','').replace('-','').capitalize()
            
            # Construir el identificador del centro
            centro_id = f"{CENTRO}-{AREA}-{REGION}"
    
            # Obtener la coleccion de MongoDB correspondiente a los datos del sensor
            collection = enviro_data_collection
    #        collection = server_db[f"{client}_sensor_data"]
    
            # Obtener lista de fechas solicitadas en formato adecuado
            day_list = []
    
            # Obtener el rango de fechas
            if request.method == 'POST':
                date_range = request.json
                start_date = datetime.strptime(date_range['startDate'], '%d/%m/%Y')
                end_date = datetime.strptime(date_range['endDate'], '%d/%m/%Y')
            else:
                end_date = datetime.now()
                start_date = end_date - timedelta(days=6)
    
            # Consulta a la base de datos para obtener todos los documentos en el rango de fechas
            query = {
                'centroId': centro_id,
                'fecha': {
                    '$gte': start_date.strftime("%Y-%m-%d"),
                    '$lte': end_date.strftime("%Y-%m-%d")
                }
            }
            documents = collection.find(query)
    
            print(f'Solicitado {len(day_list)} dias para el centro {centro_id}')
    
            # Inicializar un diccionario para almacenar los datos
            all_stations_data  = {}
    
            # Procesar los documentos recuperados
            for document in documents:
                if document:
                    # Buscar el documento correspondiente al centro y la fecha
                    day_data = _get_all_station_data(document, all_day_data=True)
                    
                    for station_name, data in day_data.items():
                        if station_name not in all_stations_data:
                            all_stations_data[station_name] = []
                        all_stations_data[station_name].extend(data)
    
            dict_to_send = {
                'batch_data' : True,
                'sensor_data' : [all_stations_data[k] for k in sorted(all_stations_data)]
            }
            print(f'enviando {len(dict_to_send["sensor_data"][0])} datos')
            
            return jsonify(dict_to_send), 200 
        
        except Exception as e:
            print(f'Error en multiple_days_data linea {e.__traceback__.tb_lineno}: \n{e}')
            return jsonify({"mensaje": f"Error al procesar los datos: {str(e)}"}), 500

    elif SERVICE == "nanoox":
        print(f'Datos Nanoox se obtiene de otra forma')
        return jsonify({"mensaje": "Datos Nanoox se obtiene de otra forma"}), 500
    elif SERVICE == "scapp":
        print(f'Datos Scapp se obtiene de otra forma')
        return jsonify({"mensaje": "Datos Scapp se obtiene de otra forma"}), 500
    elif SERVICE == "pss":
        print(f'Datos PSS se obtiene de otra forma')
        return jsonify({"mensaje": "Datos PSS se obtiene de otra forma"}), 500


# ? ----- Recepcion y guardado de datos con reenvio automatico----- ? # 


@app.route('/<SERVICE>/new_data/<REGION>/<AREA>/<CENTRO>', methods=['POST'])
def new_data(SERVICE, REGION, AREA, CENTRO):    

    print("++++++++++++ENTRANDO A RX DE DATOS PREVIO A SERVICE+++++++++++")
    if SERVICE == "nanoox":
        try:
            print("++++++++++++ENTRANDO A RX DE DATOS NANOOX+++++++++++")

            # Estandarizar inputs usando list comprehension
            REGION, AREA, CENTRO = [
                x.replace(' ', '').replace('-', '').capitalize() 
                for x in (REGION, AREA, CENTRO)
            ]
            centro_id = f"{CENTRO}-{AREA}-{REGION}"


            centro_id_nanoox = f"{REGION}-{AREA}-{CENTRO}"
            client = get_db()
            nanoox_db = client[nanoox_database_name]
            # print(nanoox_db)
            # print(nanoox_db.list_collection_names)
            
        #     # Obtener el JSON de la solicitud
            received_json = request.json
            print(f"{centro_id} Json recibido:")                    
            print(received_json.keys())
            # print(f"{' Json recibido ':*^40}")
            # pprint(received_json)

            
                
    


            if not received_json:
                return jsonify({"mensaje": "Datos no recibidos"}), 400
            
            # Determinar el formato de los datos
            data_type = tipo_de_datos(received_json)

            
            flag_new_version = "control_version" in received_json

            if flag_new_version:
                print(f"{centro_id_nanoox}: Datos recibidos version  ITG ANEC")
            
            else:
                print(f"{centro_id_nanoox}: Datos recibidos version  ITG ALLWARE")


            # print(f"El dato recibido es del tipo {data_type}")
            if data_type == "dict":
                print(f"Nanoox {centro_id_nanoox}: Procesando datos en formato simple (dict)")
                process_sensor_data(nanoox_db, 
                                    received_json.get("data", {}), 
                                    centro_id_nanoox, 
                                    new_version = flag_new_version
                                    )

            elif data_type == "nested":
                print(f"Nanoox {centro_id_nanoox}: Procesando datos en formato anidado (list)")
                for item in received_json.get("data", []):                    
                    process_sensor_data(nanoox_db,item, 
                                        centro_id_nanoox,
                                        new_version = flag_new_version)
            else:
                print(f"Nanoox {centro_id_nanoox}: Formato de datos no reconocido")
                return jsonify({"mensaje": "Datos recibidos erroneamente por servidor"}), 422
            
        #     print(f"Recibido dato de cliente {REGION}/{AREA}/{CENTRO}")
        #     return jsonify({"mensaje": "Datos recibidos exitosamente por servidor"}), 200
            return jsonify({"mensaje": "Datos recibidos exitosamente por servidor"}), 200

        
        except Exception as e:
            # linea_error = e.__traceback__.tb_lineno if e.__traceback__ else "desconocida"
        #     print(f"Error en new_data linea {linea_error}: {e}")
            return jsonify({"mensaje": "Error interno del servidor"}), 500
    
    # Rutas para otros servicios
    elif SERVICE in ("enviro", "scapp", "pss"):
        print(f"Datos {SERVICE} se guardan de otra forma")
        return jsonify({"mensaje": f"Datos {SERVICE} se guardan de otra forma"}), 500

    else:
        return jsonify({"mensaje": "Servicio no reconocido"}), 404


@app.route( '/<SERVICE>/<REGION>/<AREA>/<CENTRO>', methods=['POST'] )
def post_ping(SERVICE ,REGION, AREA, CENTRO):

    print("post pong")
    # pprint( request.json)

    print(REGION, AREA,CENTRO)
    
    
    try:
        client = get_db()
        CENTRO  = CENTRO.replace(' ','').replace('-','').replace("<", "").replace(">", "").capitalize()
        REGION  = REGION.replace(' ','').replace('-','').replace("<", "").replace(">", "").capitalize()
        AREA    = AREA.replace(' ','').replace('-','').replace("<", "").replace(">", "").capitalize()
        
        if SERVICE == "ENVIRO":
                
            # Obtener la coleccion de MongoDB correspondiente a los datos de sensores del cliente
#           collection = server_db[f"{client}_sensor_data"]
            # collection = enviro_data_collection
            collection = client[enviro_database_name].data


    
            # Obtener los datos del sensor del cuerpo de la solicitud POST
            all_sensors_data = request.json
    
            # Verificar si se recibieron datos
            if not all_sensors_data:
                return jsonify({"mensaje": "Datos no recibidos"}), 400
    
            all_sensors_data = all_sensors_data.get('data')
    
            if not all_sensors_data:
                return jsonify({"mensaje": "Datos no recibidos"}), 400
    
            # print(f'all_sensors_data: \ttipo: {type(all_sensors_data)} \tlen: {len(all_sensors_data)} ')
    
            # Definir el ID del centro:
            centroId = f'{CENTRO}-{AREA}-{REGION}'
            
    
            print("LLEGAMOS A GUARDAR DATOS")
    
            # Guardar elementos recibidos en BD
            _save_client_data( collection, all_sensors_data, centroId )
            
            
            # Revisar si metadata esta actualizada para este centro
            check_metadata( REGION, AREA, CENTRO)
            
            print(f'Guardado datos desde centro {centroId}')
            return jsonify({"mensaje": "Datos recibidos exitosamente"}), 200

        elif SERVICE == "nanoox":
            print(f'Datos Nanoox se guardan de otra forma')
            return jsonify({"mensaje": "Datos Nanoox se guardan de otra forma"}), 500
        elif SERVICE == "scapp":
            print(f'Datos Scapp se guardan de otra forma')
            return jsonify({"mensaje": "Datos Scapp se guardan de otra forma"}), 500
        elif SERVICE == "pss":
            print(f'Datos PSS se guardan de otra forma')
            return jsonify({"mensaje": "Datos PSS se guardan de otra forma"}), 500
   
    except Exception as e:
        print(f'Error en post_ping linea {e.__traceback__.tb_lineno}: \n{e} ')
        return jsonify({"mensaje": f"Error al procesar los datos: {str(e)}"}), 500

    return 'server pong'


# !--- Generacion de Reporteria ---!


# ? Generar reportes
#Enviro
@app.route('/<SERVICE>/generate_data_report/<REGION>/<AREA>/<CENTRO>', methods=['POST'])
def generate_server_data_report(SERVICE, REGION, AREA, CENTRO):
    if SERVICE == "enviro":
        print("GENERANDO REPORTE ENVIRO")
        client = get_db()
        enviro_data_collection = client[enviro_database_name].data
        REGION  = REGION.replace(' ','').replace('-','').capitalize()
        AREA    = AREA.replace(' ','').replace('-','').capitalize()
        CENTRO  = CENTRO.replace(' ','').replace('-','').capitalize()
        
        # Construir el identificador del centro
        centro_id = f"{CENTRO}-{AREA}-{REGION}"
    
        # Obtener la coleccion de MongoDB correspondiente a los datos del sensor
        collection = enviro_data_collection
    #    collection = server_db[f"{client}_sensor_data"]
        
        # Obtener rango de fechas
        date_range = request.json
        # Asumir que las fechas vienen en formato "DiA/MES/AÑO"
        start_date = datetime.strptime(date_range['startDate'], '%d/%m/%Y')
        end_date = datetime.strptime(date_range['endDate'], '%d/%m/%Y')
        
        # Crear array de fechas solicitadas
        date_array = []
        delta = end_date - start_date
        for i in range(delta.days + 1):
            day = start_date + timedelta(days=i)
            date_array.append(day.strftime("%Y-%m-%d"))
        
        # Asignar nombre de reporte
        if start_date == end_date:
            date = date_range['startDate'].replace('/','-')
            excel_save_path = f"{REPORTS_DIRECTORY}/{centro_id}_{date}.xlsx"
            excel_send_path = "./reportes/asignacion_selectoras.xlsx"
        else:
            i_date = date_range['startDate'].replace('/','-')
            f_date = date_range['endDate'].replace('/','-')
            excel_save_path = f"{REPORTS_DIRECTORY}/{centro_id}_{i_date}_{f_date}.xlsx"
            excel_send_path = f"./reportes/{centro_id}_{i_date}_{f_date}.xlsx"

        try:
            if generate_report_from_server( date_array, collection, centro_id, excel_save_path ):
                

                # Marcar el archivo para ser eliminado despues de la solicitud
                @after_this_request
                def remove_file(response):
                    try:
                        os.remove(excel_save_path)
                    except Exception as error:
                        app.logger.error("Error al eliminar archivo", error)
                    return response
        
                return send_file(excel_send_path, as_attachment=True)
            
            else:
                return jsonify({"mensaje": f"Error generar reporte"}), 500
        except Exception as e:
            print(e, e.__traceback__.tb_lineno)
            return jsonify({"mensaje": f"Error generar reporte"}), 500
        
    elif SERVICE == "nanoox":
        print(f'Nanoox NO posee reporteria')
        return jsonify({"mensaje": "Nanoox NO posee reporteria"}), 500
    elif SERVICE == "scapp":
        print(f'Datos Scapp NO posee reporteria')
        return jsonify({"mensaje": "Scapp NO posee reporteria"}), 500
    elif SERVICE == "pss":
        print(f'Datos PSS NO posee reporteria')
        return jsonify({"mensaje": "PSS NO posee reporteria"}), 500

#Nanoox
@app.route('/<SERVICE>/generate_nanoox_report/<REGION>/<AREA>/<CENTRO>/<CONTROL>', methods=['POST']) #/<CONTROL>
def generate_nanoox_report(SERVICE, REGION, AREA, CENTRO, CONTROL): #, CONTROL
    print(SERVICE)
        
    SERVICE = SERVICE.upper()
    if SERVICE =="NANOOX":
        print("*********** °ENTRANDO A CREAR REPORTE NANOOX! ********")
        client = get_db()
        nanoox_db = client[nanoox_database_name]

        data = request.json
        file_flag = None
        control = str(CONTROL)
        """ print(f"{control=}")


        print(f"{data=}")
        print(f"{CENTRO=}")
        print(f"{AREA=}")
        print(f"{REGION=}") """
        # REGION = 'loslagos'
        # print(f"HARD {REGION=}")
        centro_id = f"{CENTRO}-{AREA}-{REGION}"

        # print(f"{centro_id=}")
        print(f"{REPORTS_DIRECTORY=}")     

        logger.debug(f"{REPORTS_DIRECTORY=}")   
    
        # file_name = f"{centro_id}-reporte_sensores.xlsx"
        # file_remove = str(excel_save_path+file_name)
        capitalized_center_name = CENTRO.capitalize()
        print(f'{request.json=}')
        ret_status, dest_path= gen_data_report(rango_fechas=data, 
                     control_type=CONTROL, 
                    excel_save_path= REPORTS_DIRECTORY,                                        

                     db_name= nanoox_database_name,                                                
                     centro_name= CENTRO)
        
        logger.debug( f"{ret_status=}")
        logger.debug(f"{ dest_path=}")
        
        
        
        # ts_request =time.time_ns()
        # # ret_status, dest_path = _gen_data_report(db= nanoox_db,  
        # #                                rango_fechas= data,                                         
        # #                                excel_save_path= REPORTS_DIRECTORY,                                        
        # #                                centro_id = capitalizar_centro_id(f"{REGION}-{AREA}-{CENTRO}"), 
        # #                                centro_name = capitalized_center_name,  
        # #                                sensores_coll_name = f"{capitalized_center_name}_sensores",
        # #                                metadata_coll_name = f"{capitalized_center_name}_metadata",                                    
        # #                                control_type= CONTROL,
        # #                                ts_request = ts_request)


        
        if ret_status is True:
            print("respuesta 200")
            file_flag = True
        # if generate_report_from_server( date_array, collection, centro_id, excel_save_path ):
            # Marcar el archivo para ser eliminado despues de la solicitud
            try:
                @after_this_request
                def remove_file(response):
                    try:
                        os.remove(dest_path) #excel_save_path
                        ...
                    except Exception as error:
                        app.logger.error("Error al eliminar archivo", error)
                    return response

                # return send_file(file_remove, as_attachment=True)
                if file_flag is True:
                    print("entramos a mandar reporte")
                    try:
                        relative_file_path = dest_path.split("reportes")[-1]
                    except Exception as e:
                        print(e)

                    print(f"{relative_file_path=}")
                    # return send_file(f"./reportes{relative_file_path}", as_attachment=True, download_name= relative_file_path.replace(f"_{ts_request}", ""))
                    return send_file(f"./reportes{relative_file_path}", as_attachment=True, download_name= relative_file_path)
                
                    # return send_file(dest_path, as_attachment=True)
                
            except Exception as e:
                return jsonify({"mensaje": f"No hay datos para generar reportes, con error {e}"}), 204
        
        
    
        
        elif ret_status is False:
            print("respuesta 204")
            return jsonify({"mensaje": f"No hay datos para generar reportes"}), 204
        elif ret_status is None:
            return jsonify({"mensaje": f"Error generar reporte"}), 500
    else:
        return jsonify({"mensaje": f"No existe reporte para el servicio {SERVICE} al menos en esta ruta"}), 500
    return jsonify({"mensaje": f"No existe reporte para el servicio {SERVICE} al menos en esta ruta"}), 200





@app.route('/<SERVICE>/generate_historical_report/<REGION>/<AREA>/<CENTRO>', methods=['POST'])
def generate_historical_report(SERVICE, REGION, AREA, CENTRO):
    # return jsonify({"mensaje": "Aqui se debe generar el reporte historico de control de nanoox"}), 200
    client = get_db()
    nanoox_db = client[nanoox_database_name]

    SERVICE = SERVICE.upper()
    data = request.json

    # REGION = 'loslagos'
    # print(f"HARD {REGION=}")
    centro_id = f"{CENTRO}-{AREA}-{REGION}"

    # centro_id = f"{CENTRO}-{AREA}-{REGION}"

    # excel_save_path = f"{REPORTS_DIRECTORY}/{centro_id}-reporte_historial.xlsx"
    # print(f"{REPORTS_DIRECTORY=}")    
    excel_save_path = f"{REPORTS_DIRECTORY}/"
    file_name = f"{centro_id}-reporte_historial.xlsx"
    file_remove = str(excel_save_path+file_name)

    print(f"{file_name=}")
    print(f"{file_remove=}")
    print(f"{excel_save_path=}")

    reporte_status= gen_reporte_historial(nanoox_db, data,excel_save_path, file_name )
    print(f"{reporte_status=}")
    if reporte_status is True:
        print("Hay datos y se genera excel")
    # if generate_report_from_server( date_array, collection, centro_id, excel_save_path ):
        # Marcar el archivo para ser eliminado despues de la solicitud
        try:
            @after_this_request
            def remove_file(response):
                try:
                    os.remove(file_remove)
                except Exception as error:
                    app.logger.error("Error al eliminar archivo", error)
                return response
            if reporte_status:
                return send_file(f"./reportes/{centro_id}-reporte_historial.xlsx", as_attachment=True)
            else:
                return jsonify({"mensaje": f"No hay datos para generar reportes, con error {e}"}), 204
        except Exception as e:
            return jsonify({"mensaje": f"No hay datos para generar reportes, con error {e}"}), 204
    elif reporte_status is False:
        print("NO Hay datos y NO se genera excel")
        return jsonify({"mensaje": f"No hay datos para generar reportes, con error"}), 204

    elif reporte_status is None:
        print("Error servidor")
        return jsonify({"mensaje": f"Error generar reporte"}), 500


@app.route('/<SERVICE>/generate_generator_report/<REGION>/<AREA>/<CENTRO>', methods=['POST'])
def generate_generator_report(SERVICE, REGION, AREA, CENTRO):
    # return jsonify({"mensaje": "Aqui se debe generar el reporte historico de control de nanoox"}), 200
    client = get_db()
    nanoox_db = client[nanoox_database_name]

    SERVICE = SERVICE.upper()
    data = request.json
    print("###########ENTRANDO A GENERAR REPORTE GENERADOR ####################")
    centro_id = f"{CENTRO}-{AREA}-{REGION}"
    centro_id = convertir_mal_menor(centro_id)

    excel_save_path = f"{REPORTS_DIRECTORY}/"
    file_name = f"{centro_id}-reporte_generador.xlsx"
    file_remove = str(excel_save_path+file_name)

    print(f"{file_name=}")
    print(f"{file_remove=}")
    print(f"{excel_save_path=}")

    reporte_status= gen_reporte_generadores(nanoox_db, data,excel_save_path, file_name )

    if reporte_status is True:
        print("Habian datos")
    # if generate_report_from_server( date_array, collection, centro_id, excel_save_path ):
        # Marcar el archivo para ser eliminado despues de la solicitud
        try:
            @after_this_request
            def remove_file(response):
                try:
                    os.remove(file_remove)
                except Exception as error:
                    app.logger.error("Error al eliminar archivo", error)
                return response

            return send_file(f"./reportes/{centro_id}-reporte_generador.xlsx", as_attachment=True)

        except Exception as e:
            return jsonify({"mensaje": f"No hay datos para generar reportes, con error {e}"}), 204
    elif reporte_status is False:
        print("No habian datos en la fecha, respondemos 204")
        return jsonify({"mensaje": f"No hay datos para generar reportes, con error {e}"}), 204
    elif reporte_status is None:
        print("Error servidor")
        return jsonify({"mensaje": f"Error generar reporte"}), 500

# !--- CRUD de Usuarios ---!

#Crear usuario
# @app.route('/crear_usuario/<user>', methods=['POST'])
# def api_crear_usuario(user):
#     client = get_db()
#     users_collection = client[user_database_name].users
#     users_historial_collection = client[user_database_name].historial_users

#     data_user = request.json
#     print(f'info usuario a crear: {data_user}')
#     usuario_actual = user

#     resultado, codigo_estado = crear_usuario(users_collection, data_user, usuario_actual)

#     insert_log_in_user_history(users_historial_collection, usuario_actual, data_user.get('username'), 'add_user')

#     return resultado, codigo_estado

@app.route('/crear_usuario', methods=['POST'])
@jwt_required()
def api_crear_usuario():
    # print("ENTRANDO A LA NUEVA RUTA DE CREAR USUARIOS")
    client = get_db()
    users_collection = client[user_database_name].users
    users_historial_collection = client[user_database_name].historial_users

    usuario_actual = get_jwt_identity()  # obtenido desde el token
    data = request.json

    # Asegurar que sea lista
    if isinstance(data, dict):
        data = [data]

    resultados = []

    for data_user in data:
        try:
            print(f'Intentando crear: {data_user=}')
            resultado, codigo_estado = crear_usuario(users_collection, data_user, usuario_actual)

            # Log si fue exitoso
            if codigo_estado == 201:
                insert_log_in_user_history(
                    users_historial_collection, usuario_actual, data_user.get('username'), 'add_user'
                )

            resultados.append({
                "usuario": data_user.get('username'),
                "estado": codigo_estado,
                "resultado": resultado.get_json() if hasattr(resultado, 'get_json') else resultado
            })
        except Exception as e:
            print(f"Error al crear usuario {data_user.get('username')}: {e}")
            resultados.append({
                "usuario": data_user.get('username'),
                "estado": 500,
                "resultado": f"Error inesperado: {str(e)}"
            })

    return jsonify({"resultados": resultados}), 207  # 207: Multi-Status


#Eliminar usuario
@app.route('/delete_user/<user>/<delete_user>', methods=['DELETE'])
def delete_user(user, delete_user):
    """
    Endpoint para eliminar un usuario existente en la base de datos.
    
    Parametros:
    - username (str): Nombre de usuario del usuario a eliminar.

    Retorna:
    - Un objeto JSON con el resultado de la operacion y un codigo de estado HTTP.
    
    Ejemplo de uso:
    DELETE /delete_user/nombre_de_usuario
    """
    client = get_db()
    users_collection = client[user_database_name].users
    users_historial_collection = client[user_database_name].historial_users

    usuario_actual = user
    usuario_delete = delete_user
    print(f'usuario_actual: {usuario_actual} \n')
    print(f'usuario_delete :{usuario_delete} \n')
    usuario_actual_info = users_collection.find_one({'username': usuario_actual})
    usuario_delete_info = users_collection.find_one({'username': usuario_delete})
    #print(f'usuario_actual_info: {usuario_actual_info} \n')
    cargo_actual = usuario_actual_info['cargo']
    cargo_edit = usuario_delete_info['cargo']

    # if cargo_actual == 'Operario':
    #     # return "No posee permisos para editar/eliminar"
    #     return jsonify({"mensaje": "No posee permisos para editar/eliminar."}), 400

    if verificar_permisos_de_edicion(cargo_actual, cargo_edit):
        try:
            # Llamada a la funcion que elimina el usuario
            status_code = eliminar_usuario(users_collection, usuario_delete)
            
            # Respuesta en caso de exito
            if status_code == 200:
                insert_log_in_user_history(users_historial_collection, usuario_actual, usuario_delete, 'delete_user')
                return jsonify({"mensaje": f"Usuario '{usuario_delete}' eliminado correctamente."}), 200
            # Respuesta en caso de que no se encuentre el usuario o no se elimine
            else:
                return jsonify({"mensaje": f"Usuario '{usuario_delete}' no encontrado o no se elimino ningun usuario."}), 400
        except Exception as e:
            # Respuesta en caso de un error inesperado
            return jsonify({"mensaje": f"Error inesperado: {str(e)}"}), 500
    else:
        return jsonify({"mensaje": "No se puede continuar por falta de rango."}), 400



@app.route('/obtener_usuarios', methods=['GET'])
def api_obtener_usuarios():
    try:
        client = get_db()
        users_collection = client[user_database_name].users
        # Aqui asumimos que 'usuarios_collection' es una variable que ya tienes definida
        # y que representa la coleccion de usuarios en tu base de datos de MongoDB.

        # Seleccionamos solo los campos que queremos retornar, excluyendo el campo password por seguridad y MongoDB ID .
        usuarios = users_collection.find({}, {'_id':0 , 'password': 0})

        # Convertimos los usuarios a una lista para poder enviarlos como JSON
        lista_usuarios = list(usuarios)

        # Retornamos la lista de usuarios como un JSON
        return jsonify(lista_usuarios), 200
    except Exception as e:
        # En caso de error, retornamos un mensaje indicando el fallo
        return jsonify({"mensaje": "Error al obtener la lista de usuarios", "error": str(e)}), 500

#Editar usuario
# @app.route('/editar_usuario/<user>/<edited_user>', methods=['POST'])
# def api_editar_usuario(user, edited_user):
#     client = get_db()
#     users_collection = client[user_database_name].users
#     users_historial_collection = client[user_database_name].historial_users

#     data_user = request.json
#     print(f"{data_user=}")
# #    usuario_edit = data_user.get('username')
#     usuario_edit = edited_user
#     usuario_actual = user
#     usuario_actual_info = users_collection.find_one({'username': usuario_actual})
#     usuario_edit_info = users_collection.find_one({'username': usuario_edit})
#     #print(f'usuario_actual_info: {usuario_actual_info} \n')
#     cargo_actual = usuario_actual_info['cargo']
#     cargo_edit = usuario_edit_info['cargo']

#     print(f"{cargo_actual=}")
#     print(f"{cargo_edit=}")

#     if cargo_actual == 'Operario':
#         return jsonify({"mensaje": "No posee permisos para editar/eliminar."}), 400

#     if verificar_permisos(cargo_actual, cargo_edit):
#         # print("ENTRAMOS AL EDIT USUARIOS DESPUES DE VERIFICAR PERMISOS. DESPUES FAIA")
#     # if verificar_permisos_de_edicion(cargo_actual, cargo_edit):
#         # Asumiendo que la solicitud tiene un cuerpo de tipo 'application/json'.
#         # data_user = request.json
#         username = usuario_edit
#         try:
#             editar_usuarios(users_collection, username, data_user)
#             insert_log_in_user_history(users_historial_collection, usuario_actual, usuario_edit, 'edit_user')
#             return jsonify({"mensaje": f"Usuario {username} actualizado exitosamente en"}), 201
#         except Exception as e:
#             return jsonify({"mensaje": "No se pudo registrar el usuario.", "error": str(e)}), 500
#     else:
#        return jsonify({"mensaje": "No se puede continuar por falta de rango."}), 400 


# —–– Ruta multiple: editar uno o varios usuarios a la vez —–––––––––––––––
#OLD ROUTE
# @app.route('/editar_usuarios', methods=['POST'])
# @jwt_required()
# def api_editar_usuarios_multiples():
#     """
#     JSON in POST:
#     {
#     "edits": [
#         {
#         "username": "usuarioA",
#         "updates": {
#             "correo": "nuevoA@ejemplo.com",
#             "cargo": "Jefe Area",
#             "permisos": {
#             "loslagos": {
#                 "chiloe": {
#                 "chalihue-chiloe-loslagos": {
#                     "enviro": { "status": true },
#                     "nanoox": { "status": false },
#                     "pss":    { "status": true },
#                     "scapp":  { "status": true }
#                 }
#                 }
#             }
#             }
#         }
#         }
#         // …otros objetos de edicion…
#     ]
#     }
#     """
#     # Obtenemos el editor desde el token JWT
#     editor = get_jwt_identity()

#     client    = get_db()
#     users_col   = client[user_database_name].users
#     history_col = client[user_database_name].historial_users

#     body  = request.get_json(force=True) or {}
#     edits = body.get('edits')
#     if not isinstance(edits, list) or not edits:
#         return jsonify({'error': "Se requiere un array 'edits' no vacio."}), 400

#     # validar editor en BD
#     editor_info = users_col.find_one({'username': editor})
#     if not editor_info:
#         return jsonify({'error': 'Editor no encontrado.'}), 404
#     cargo_editor = editor_info['cargo']
#     if cargo_editor == 'Operario':
#         return jsonify({'error': 'No posee permisos para editar.'}), 403

#     results = []
#     for edit in edits:
#         username = edit.get('username')
#         updates  = edit.get('updates')
#         item = {'username': username}

#         # validaciones basicas
#         if not username or not isinstance(updates, dict):
#             item.update({'status': 'error', 'msg': 'Payload invalido.'})
#             results.append(item)
#             continue

#         target_info = users_col.find_one({'username': username})
#         if not target_info:
#             item.update({'status': 'error', 'msg': 'Usuario no encontrado.'})
#             results.append(item)
#             continue

#         # jerarquia
#         if not verificar_permisos(cargo_editor, target_info['cargo']):
#             item.update({'status': 'denied', 'msg': 'Rango insuficiente.'})
#             results.append(item)
#             continue

#         # aplicar update
#         try:
#             editar_usuarios(users_col, username, updates)
#             insert_log_in_user_history(
#                 history_col,
#                 editor,
#                 username,
#                 'edit_user',
#             )
#             item.update({'status': 'ok'})
#         except Exception as e:
#             item.update({'status': 'error', 'msg': str(e)})

#         results.append(item)

#     return jsonify({'results': results}), 207

# NEW
@app.route('/editar_usuarios', methods=['POST'])
@jwt_required()
def api_editar_usuarios_multiples():
    """
    Permite editar multiples usuarios, incluyendo transformacion de permisos simplificados para Jefe Area y Jefe Region.
    {
    "edits": [
        {
        "username": "usuarioA",
        "updates": {
            "correo": "nuevoA@ejemplo.com",
            "cargo": "Jefe Area",
            "permisos": {
            "loslagos": {
                "chiloe": {
                "chalihue-chiloe-loslagos": {
                    "enviro": { "status": true },
                    "nanoox": { "status": false },
                    "pss":    { "status": true },
                    "scapp":  { "status": true }
                }
                }
            }
            }
        }
        }
        // …otros objetos de edicion…
    ]
    }
    """
    editor = get_jwt_identity()
    

    client    = get_db()
    users_col   = client[user_database_name].users
    history_col = client[user_database_name].historial_users

    # body  = request.get_json(force=True) or {}
    body = request.json
    print(f"{body=}")
    edits = body.get('edits')
    if not isinstance(edits, list) or not edits:
        return jsonify({'error': "Se requiere un array 'edits' no vacio."}), 400

    editor_info = users_col.find_one({'username': editor})
    if not editor_info:
        return jsonify({'error': 'Editor no encontrado.'}), 404
    cargo_editor = editor_info['cargo']
    if cargo_editor == 'Operario':
        return jsonify({'error': 'No posee permisos para editar.'}), 403

    results = []
    for edit in edits:
        username = edit.get('username')
        updates  = edit.get('updates')
        item = {'username': username}

        if not username or not isinstance(updates, dict):
            item.update({'status': 'error', 'msg': 'Payload invalido.'})
            results.append(item)
            continue

        target_info = users_col.find_one({'username': username})
        if not target_info:
            item.update({'status': 'error', 'msg': 'Usuario no encontrado.'})
            results.append(item)
            continue

        # Jerarquia de cargos
        if not verificar_permisos(cargo_editor, target_info['cargo']):
            item.update({'status': 'denied', 'msg': 'Rango insuficiente.'})
            results.append(item)
            continue

        try:
            editar_usuarios(users_col, username, updates, editor_info=editor_info)
            insert_log_in_user_history(
                history_col,
                editor,
                username,
                'edit_user',
            )
            item.update({'status': 'ok'})
        except Exception as e:
            item.update({'status': 'error', 'msg': str(e)})

        results.append(item)

    return jsonify({'results': results}), 207

@app.route('/ediciones_usuario', methods=['GET'])
@jwt_required()
def usuarios_a_editar():
    username = get_jwt_identity()

    client = get_db()
    users_collection = client[user_database_name].users
    
    lista_editables = []
    proyeccion = {"_id": 0, "password": 0, "refresh_token": 0, "access_token": 0}
    #username = username
    usuario_actual_info = users_collection.find_one({'username': username})
    cargo_actual = usuario_actual_info['cargo']
    # print(f"{usuario_actual_info=}")
    # print(f"{cargo_actual=}")    
    # permisos_actual = usuario_actual_info['permisos']
    for usuario in users_collection.find():
        if verificar_permisos(cargo_actual, usuario['cargo']):
            resultados = users_collection.find({'username': usuario['username']}, proyeccion)
            lista_editables.append(resultados[0])
    if lista_editables is not None:
        print(f"{lista_editables=}")
        return lista_editables
    else:
        #return "El ususario no tiene usuarios editables"
        return jsonify({"mensaje": f"El usuario {username} no tiene usuarios editables"}), 401

#!--Reestablecer contraseña via e-mail--¡#
@app.route('/get_link', methods=['POST'])
def reset_password():
    """
    A partir del nombre de usuario, obtiene su email desde la DB y envia un e-mail al correo
    del usuario registrado en DB con un token que expira en 1 hora
    """
    print("Entrando a get_link!")
    #1. Validaciones
    data = request.json
    print(f"{data=}")

    # Definir el orden de preferencia de las llaves
    claves = ['username', 'correo']

    # Busca la primera clave con valor no vacio
    seleccion = next((k for k in claves if data.get(k)), None) # Determina la llave seleccionada
    valor = data[seleccion] #Valor proveniente del JSON de FE
    if not seleccion:
        return jsonify({'Error': 'Ni username ni correo tienen valor.'}), 400

    #?TODO: Agregar TRY/EXCEPT
    #2. Conexion a DB y coleccion
    client = get_db()
    users_collection = client['users']['users']

    #3. Query a DB para validar existencia de usuario
    if seleccion == 'username':
        user = users_collection.find_one({'username': valor})
        if not user:
            return jsonify({'Error': f'El usuario {valor} no se encuentra registrado. Intente con otro ususario o hable con el administrador para crear el usuario {valor}'}), 400
        correo = user.get('correo', {})
        username = valor
        print(f"{username=}")
        print(f"{correo=}")
    elif seleccion == 'correo':
        user = users_collection.find_one({'correo': valor})
        if not user:
            return jsonify({'Error': f'El usuario {valor} no se encuentra registrado. Intente con otro ususario o hable con el administrador para crear el usuario {valor}'}), 400
        correo = valor
        username = user.get('username', {})
        print(f"{username=}")
        print(f"{correo=}")
    
    if not user:
        return jsonify({'Error': 'Usuario no encontrado.'}), 404
    if not correo:
        return jsonify({'Error': 'Usuario no posee correo asociado.'}), 404
    

    #4. Generar JWT de restablecimiento con claim 'reset'
    expires = timedelta(hours=1)
    token = create_reset_token(username, expires)
    # print(f"{token=}")
    print("Decodificando TOKEN")

    #5. Extraer JTI y almacenar en el documento del usuario
    decoded = decode_reset_token(token)
    print(f"{decoded=}")
    jti = decoded.get('jti')
    expires_at = datetime.now() + expires
    print("Insertando JTI en db")
    try:
        users_collection.update_one(
            {'username': username},
            {'$set': {
                'reset_jti': jti,
                'reset_expires_at': expires_at
            }}
        )
        print("><> JTI en DB")
    except Exception as e:
        print(f"Error al insertar JTI para cambio de clave del usuario {username}': {e} {e.__traceback__.tb_lineno}")
        return jsonify({'Error': 'Server Internal Error.'}), 500

    print("Entrando a generar link")
   #6. Generar enlace de restablecimiento (dominio de produccion)
    link = url_for('reset_token', token=token, _external=True)
    print("Entrando a enviar mail")
    #7. Enviar correo a usuario
    send_automatic_email(username, correo, link)
    print(f"Enlace enviado a usuario/correo {username}/{correo} para restablecer la contraseña")
    return jsonify({'Message': 'Enlace enviado para restablecer la contraseña.'}), 200


@app.route('/ResetPassword/<token>', methods=['POST'])
def reset_token(token):
    """
    Ruta dinamica para ingresar nueva contraseña solo si el token de acceso es valido (nombre, clave_secreta y duracion).
    De modificar exitosamente la clave, genera un log en historial de usuarios.
    """
    # POST: procesar formulario o JSON
    #1. Verificar y extraer username del token
    try:
        decoded = decode_reset_token(token)
        print(f"reset {decoded=}")
    except Exception:
        return jsonify({'Error': 'Token invalido o expirado.'}), 403
    
    #2. Validar claim 'reset'
    if not decoded.get('reset'):
        return jsonify({'Error': 'Token no valido para restablecimiento.'}), 403

    #3. Obtener ID unico deL Token
    jti = decoded.get('jti')

    #4. Conexion a DB
    client = get_db()
    users_db = client['users']
    users_collection = users_db.users
    users_historial_collection = users_db.historial_users

    #5. Buscar usuario que tenga este JTI activo
    user = users_collection.find_one({
        'reset_jti': jti,
        'reset_expires_at': {'$gt': datetime.now()}
    })
    print(f"{user=}")
    if not user or user==None:
        print('Token ya usado o expirado.')
        return jsonify({'Error': 'Token ya usado o expirado.'}), 403
    
    username = user['username']
    if not username:
        return jsonify({'Error': 'No hay datos del usuario.'}), 404

    #6. Obtener nueva contraseña
    data = request.get_json(silent=True) or request.form
    new_password = data.get('password')
    if not new_password:
        return jsonify({'Error': 'Se requiere la nueva contraseña.'}), 406

    #7. Actualizar contraseña en DB y limpiar JTI    
    result = users_collection.update_one(
        {'username': username},
        {'$set': {'password': generate_password_hash(new_password)},
         '$unset': {'reset_jti': '', 'reset_expires_at': ''}}
    )
    if result.modified_count == 0:
        return jsonify({'Error': 'Error al actualizar contraseña.'}), 500
    print("Se ha modificado exitosamente la contraseña en la DB")
    #8. Registro de log de restablecimiento exitoso tras actualizar password
    # timestamp = datetime.now()
    # log_entry = {
    #     'date': timestamp,
    #     'usuario': username,
    #     'msg': f'El usuario {username} modifico su contraseña a las {timestamp.isoformat()}'
    # }
    # users_historial_collection.insert_one(log_entry)
    insert_log_in_user_history(users_historial_collection, username, None, 'renew_password')
    # print(f"Log insertado en Histortial de Usuarios\n")
    print(f'Contraseña actualizada correctamente para el usuario {username}.')
    return jsonify({'Message': f'Contraseña actualizada correctamente para el usuario {username}.'}), 200

@app.route('/get_users_historical_logs', methods=['GET'])
def get_logs():
    """
    Endpoint to fetch user action logs by specific date, date range, and type.

    Query parameters:
    - type: one of ['edit_user', 'add_user', 'delete_user', 'renew_password'] or 'all' (default 'all')
    - date: single day in 'YYYY-MM-DD' format (optional)
    - start_date: beginning of range in 'YYYY-MM-DD' format (optional)
    - end_date: end of range in 'YYYY-MM-DD' format (optional)

    Examples:
    /logs?date=2025-06-19&type=edit_user
    /logs?start_date=2025-06-01&end_date=2025-06-10&type=all
    """
    client = get_db()
    users_db = client['users']
    users_historial_collection = users_db.historial_users

    # Retrieve query parameters
    type_param = request.args.get('type', 'all')
    date_str = request.args.get('date')
    start_str = request.args.get('start_date')
    end_str = request.args.get('end_date')

    query = {}

    # Filter by type if specified and not 'all'
    if type_param and type_param.lower() != 'all':
        query['type'] = type_param

    # Single-day filter
    if date_str:
        try:
            date_obj = datetime.strptime(date_str, '%Y-%m-%d')
        except ValueError:
            return jsonify({'error': 'Invalid date format, use YYYY-MM-DD'}), 400
        next_day = date_obj + timedelta(days=1)
        query['date'] = {'$gte': date_obj, '$lt': next_day}

    # Range filter (only if date not provided)
    elif start_str or end_str:
        date_filter = {}
        if start_str:
            try:
                start_obj = datetime.strptime(start_str, '%Y-%m-%d')
            except ValueError:
                return jsonify({'error': 'Invalid start_date format, use YYYY-MM-DD'}), 400
            date_filter['$gte'] = start_obj
        if end_str:
            try:
                end_obj = datetime.strptime(end_str, '%Y-%m-%d')
            except ValueError:
                return jsonify({'error': 'Invalid end_date format, use YYYY-MM-DD'}), 400
            # Include the end date fully by adding one day
            date_filter['$lt'] = end_obj + timedelta(days=1)
        if date_filter:
            query['date'] = date_filter
    print(f"{list(query)=}")
    # Execute the query and sort by date ascending
    # cursor = users_historial_collection.find(query).sort('date', 1)
    cursor = list(users_historial_collection.find(query, {'_id': 0}).sort('date', 1))
    print(f"{cursor=}")
    # Prepare results for JSON serialization
    results = []
    for doc in cursor:
        print(f"{list(doc)=}")
        # Optionally format date as string
        if 'date' in doc and isinstance(doc['date'], datetime):
            doc['date'] = doc['date'].strftime('%Y-%m-%dT%H:%M:%S')
        results.append(doc)

    return jsonify(results), 200

@app.route('/report_gps/<REGION>/<AREA>/<CENTRO>', methods=['POST'])
def report_position(REGION, AREA, CENTRO):
    try:
        data = request.get_json(force=True)
        # data = request.json
        print(f"{data=}")
        
        CENTRO = CENTRO.lower()
        AREA = AREA.lower()
        REGION = REGION.lower()

        centro_id = f"{CENTRO}-{AREA}-{REGION}"
        print(f"{centro_id=}")

    except Exception as e:
        return jsonify({'Error': f'JSON invalido {e}'}), 400
    
    lat = data.get('lat')
    lon = data.get('lon')

    if not centro_id or lat is None or lon is None:
        return jsonify({'Error': 'Faltan parametros: centro_id, lat y lon son obligatorios'}), 400

    # Convertir a float si vienen como strings
    try:
        lat = float(lat)
        lon = float(lon)
    except ValueError:
        return jsonify({'Error': 'lat y lon deben ser valores numericos'}), 400

    # Subdocumento georef con valores planos y timestamp
    coordinates = {
        'lat': lat,
        'lon': lon,
        'timestamp': datetime.now() # seg. desde epoch como float
    }

    #Conexion a DB y coleccion
    client = get_db()
    users_db = client[user_database_name]
    ciclos_collection = users_db['ciclos']
    
    result = ciclos_collection.update_one(
        {'centro_id': centro_id},
        {'$set': {'gps_coordinates.auto': coordinates}}
        # upsert=True
    )

    if result.matched_count == 0:
        return jsonify({'Error': f'No existe documento con ID {centro_id}'}), 404
        
    print(f"Datos de GPS actualizados para centro {centro_id}")
    return jsonify({'Message': f'Datos de GPS actualizados para centro {centro_id}'}), 200

#!---CRUD de Centros desde Interfaz Grafica---¡
# @app.route('/crear_centro', methods=['POST'])
# def api_crear_centro():
#     client = get_db()
#     user_db = client[user_database_name]

#     data_centros = request.json
#     print(f'data_centros: {data_centros}')


#     try:
#         resumen = ciclos_insert(client, data_centros)
#         # Puedes decidir el status según el resultado; aquí dejamos 200 con detalle.
#         return jsonify({
#             "mensaje": "Operación procesada",
#             "resumen": resumen
#         }), 200
#     except Exception as e:
#         print(f'Error: {e}')
#         return jsonify({"mensaje": "No se logró realizar la operación"}), 500


@app.route('/crear_centro', methods=['POST'])
@jwt_required()
def api_crear_centro():
    print("ENTRANDO A CREAR CENTRO!")
    username = get_jwt_identity()
    client = get_db()
    # nanoox_db = client[nanoox_database_name]
    user_db = client[user_database_name]
    data_centros = request.json
    # raw = request.json
    
    print(f'data_centros: {data_centros}')
    try:
        
        ciclos_insert(client, username, data_centros)
        permisos_superusuario(user_db)
        #New Feature!
        actualizar_permisos_jefes(data_centros, user_db)
        verificacion_status_servicios_por_centro()
        return jsonify({"mensaje": "Operacion exitosa"}), 200
    except Exception as e:
        print(f'Error: {e} {e.__traceback__.tb_lineno}')
        return jsonify({"mensaje": "No se logro realizar la operacion"}), 500
    
#!---Edicion de Centros desde Interfaz Grafica---¡#
@app.route('/obtener_centros', methods=['GET'])
@jwt_required()
def centros_editables():
    client = get_db()
    user_db = client[user_database_name]
    users_collection = user_db.users
    ciclos_collection = user_db.ciclos

    print(f"ENTRANDO A CENTROS EDITABLES")
    username = get_jwt_identity()
    # print(f'usuario_obtener_centros: {username}')
    usuario_actual_info = users_collection.find_one({'username': username})
    # print(f'usuario_actual: {usuario_actual_info}')
    cargo_actual = usuario_actual_info['cargo']
    # print(f'cargo: {cargo_actual}')
    proyeccion = {"_id": 0, "password": 0, "rol": 0, "refresh_token": 0, "access_token": 0}
    if cargo_actual not in ['Superusuario', 'Gerencia', 'Jefe Region', 'Jefe Area']:
        return jsonify({"mensaje": f"El usuario {username} no posee permisos de edicion"}), 500
    else:
        centros = obtener_lista_centros(users_collection.find_one({'username': username}, proyeccion)['permisos'])
        ciclos_centro = []
        # print(f'centros devueltos: {centros}')
        for centro in ciclos_collection.find({'centro_id': {'$in': centros}}, {"_id":0}):
            ciclos_centro.append(centro)
        # print(f'ciclos_centro devueltos: {ciclos_centro}')
        return jsonify(ciclos_centro), 200
    
@app.route('/editar_centro/<centro_id>', methods=['POST'])
@jwt_required()
def editar_centro(centro_id):
    print("ENTRANDO A EDITAR CENTRO")
    #Obtiene el nombre del usuario que esta accediendo a traves del token
    username = get_jwt_identity()
    # username = "itg"
    #Conexion a DB y colecciones
    client = get_db()
    user_db = client[user_database_name]
    users_historial_collection = user_db.historial_users
    ciclos_collection = user_db.ciclos
    #Obtencion del JSON enviado por el FE
    datos_centro = request.json
    print(f"{datos_centro=}")
    try:
        #Inserta informacion de centro en coleccion Ciclos
        actualizar_documento(ciclos_collection, {'centro_id':centro_id}, datos_centro)
        #Inserta el log de la edicion del centro en la coleccion users_historial
        insert_log_in_user_history(users_historial_collection, username, centro_id, 'edit_center')
        verificacion_status_servicios_por_centro()
        return jsonify({"mensaje": f"El centro {centro_id} se ha editado correctamente"}), 200
    except Exception as e:
        print(f'Error editar_centro: {e} {e.__traceback__.tb_lineno}')
        return jsonify({"mensaje": f"El centro {centro_id} no se ha logrado editar en este momento, intente nuevamente"}), 401

@app.route('/eliminar_centro/<centro_id>', methods=['POST'])
@jwt_required()
def eliminar_centro(centro_id):
    #Obtiene el nombre del usuario que esta accediendo a traves del token
    username = get_jwt_identity()
    # username = "itg"
    #Conexion a DB y colecciones
    client = get_db()
    user_db = client[user_database_name]
    users_historial_collection = user_db.historial_users
    ciclos_collection = user_db.ciclos
    users_collection = user_db.users
    estado_centros_collection = user_db.estados
    
    #Elimina informacion de centro en coleccion Ciclos, Estados y la elimina de todos los permisos de usuario 
    if ciclos_collection.find_one({"centro_id": centro_id}) != None:
            resultado = ciclos_collection.delete_one({'centro_id': centro_id})
            eliminar_centro_de_permisos(users_collection, centro_id)
            eliminar_estados_centro(estado_centros_collection, centro_id)
    else:
        return jsonify({"mensaje": f"El centro {centro_id} no existe"}), 500
    
    if resultado.deleted_count > 0:
        #Inserta el log de la eliminacion del centros en la coleccion users_historial
        insert_log_in_user_history(users_historial_collection, username, centro_id, 'delete_center')
        return jsonify({"mensaje": f"El centro '{centro_id}' ha sido eliminado exitosamente."}), 200
    else:
        return jsonify({"mensaje": f"El centro {centro_id} no existe"}), 500


# !--- Manejo de Alertas---!
#-------------Funciones-------------------------!
# def guardar_alertas_activas(alertas):
#     #alertas es el json enviado desde el local
#     col_enviro_alertas.insert_many(alertas)
#     print('Alertas guardadas en ITGSM')
#     return

# def guardar_alertas_activas(alertas):
#     print(f'alertas_desde_local: {alertas}')
#     try:
#         col_enviro_alertas.insert_many(alertas)
#         print("Documentos insertados exitosamente.")
#     except errors.BulkWriteError as bwe:
#         print("Error en la insercion en bloque, manejando duplicados...")
#         # Filtrar documentos duplicados
#         error_details = bwe.details
#         write_errors = error_details.get('writeErrors', [])
#         duplicated_ids = [error['op']['_id'] for error in write_errors if error['code'] == 11000]

#         if duplicated_ids:
#             print(f"IDs duplicados encontrados: {duplicated_ids}")
#             # Remover documentos duplicados del conjunto original
#             documents = [doc for doc in alertas if doc['_id'] not in duplicated_ids]
#             if documents:  # Verificar si aun hay documentos por insertar
#                 try:
#                     col_enviro_alertas.insert_many(documents)
#                     print("Documentos restantes insertados exitosamente.")
#                 except errors.BulkWriteError as bwe_inner:
#                     print("Error en la insercion en bloque (segunda vez):", bwe_inner.details)
#             else:
#                 print("No hay documentos restantes por insertar despues de filtrar duplicados.")
def guardar_alertas_activas(client, service, alertas):
    if service == "ENVIRO":
        # coleccion = col_enviro_alertas
        coleccion = client[enviro_database_name].alertas

    elif service == "NANOOX":
        # coleccion = col_nanoox_alertas
        coleccion = client[nanoox_database_name].alertas

    print(f'alertas_desde_local: {alertas}')
    try:
        coleccion.insert_many(alertas)
        print("Documentos insertados exitosamente.")
    except errors.BulkWriteError as bwe:
        print("Error en la insercion en bloque, manejando duplicados...")
        # Filtrar documentos duplicados
        error_details = bwe.details
        write_errors = error_details.get('writeErrors', [])
        duplicated_ids = [error['op']['_id'] for error in write_errors if error['code'] == 11000]

        if duplicated_ids:
            print(f"IDs duplicados encontrados: {duplicated_ids}")
            # Remover documentos duplicados del conjunto original
            documents = [doc for doc in alertas if doc['_id'] not in duplicated_ids]
            if documents:  # Verificar si aun hay documentos por insertar
                try:
                    coleccion.insert_many(documents)
                    print("Documentos restantes insertados exitosamente.")
                except errors.BulkWriteError as bwe_inner:
                    print("Error en la insercion en bloque (segunda vez):", bwe_inner.details)
            else:
                print("No hay documentos restantes por insertar despues de filtrar duplicados.")

def guardar_fallas_activas(client, service, fallas):
    if service == "ENVIRO":
        # coleccion = col_enviro_fallas
        coleccion = client[enviro_database_name].alertas_fallas


    elif service == "NANOOX":
        # coleccion = col_nanoox_fallas
        coleccion = client[nanoox_database_name].alertas_fallas

    print(f'fallas_desde_local: {fallas}')
    try:
        coleccion.insert_many(fallas)
        print("Documentos insertados exitosamente.")
    except errors.BulkWriteError as bwe:
        print("Error en la insercion en bloque, manejando duplicados...")
        # Filtrar documentos duplicados
        error_details = bwe.details
        write_errors = error_details.get('writeErrors', [])
        duplicated_ids = [error['op']['_id'] for error in write_errors if error['code'] == 11000]

        if duplicated_ids:
            print(f"IDs duplicados encontrados: {duplicated_ids}")
            # Remover documentos duplicados del conjunto original
            documents = [doc for doc in fallas if doc['_id'] not in duplicated_ids]
            if documents:  # Verificar si aun hay documentos por insertar
                try:
                    coleccion.insert_many(documents)
                    print("Documentos restantes insertados exitosamente.")
                except errors.BulkWriteError as bwe_inner:
                    print("Error en la insercion en bloque (segunda vez):", bwe_inner.details)
            else:
                print("No hay documentos restantes por insertar despues de filtrar duplicados.")

# def guardar_notas(service, centro_id, notas):
#     if service == "ENVIRO":
#         coleccion = col_enviro_notas

#     elif service == "NANOOX":
#         coleccion = col_nanoox_notas

#     print(f'notas_desde_local: {notas}')
#     print(f'centro: {centro_id}')
#     try:
#         coleccion.insert_many(notas)
#         print("Documentos insertados exitosamente.")
#     except errors.BulkWriteError as bwe:
#         print("Error en la insercion en bloque, manejando duplicados...")
#         # Filtrar documentos duplicados
#         error_details = bwe.details
#         write_errors = error_details.get('writeErrors', [])
#         duplicated_ids = [error['op']['_id'] for error in write_errors if error['code'] == 11000]

#         if duplicated_ids:
#             print(f"IDs duplicados encontrados: {duplicated_ids}")
#             # Remover documentos duplicados del conjunto original
#             documents = [doc for doc in notas if doc['_id'] not in duplicated_ids]
#             if documents:  # Verificar si aun hay documentos por insertar
#                 try:
#                     coleccion.insert_many(documents)
#                     print("Documentos restantes insertados exitosamente.")
#                 except errors.BulkWriteError as bwe_inner:
#                     print("Error en la insercion en bloque (segunda vez):", bwe_inner.details)
#             else:
#                 print("No hay documentos restantes por insertar despues de filtrar duplicados.")
def guardar_notas(client, service, centro_id, notas):
    if service == "ENVIRO":
        # coleccion = col_enviro_notas
        coleccion = client[enviro_database_name].notas


    elif service == "NANOOX":
        # coleccion = col_nanoox_notas
        coleccion = client[nanoox_database_name].notas


    print(f'notas_desde_local: {notas}')
    print(f'centro: {centro_id}')
    if notas:
        fecha = notas['fecha']
        hora = notas['hora']
        bomba_id = notas['id']
        comentario = notas['nota']['value']

        operation = [
        UpdateOne(
            {'fecha': fecha,"centro_id": centro_id, 'bomba_id': bomba_id},
            {
                '$push': {
                    'notas': {
                        'hora': hora,
                        'value': comentario
                    }
                }
            },
            upsert = True
        )
    ]
    try:
        result = coleccion.bulk_write(operation)
        print("Documentos insertados exitosamente.")
    except errors.BulkWriteError as bwe:
        print("Error en la insercion en bloque, manejando duplicados...")
        # Filtrar documentos duplicados
        error_details = bwe.details
        write_errors = error_details.get('writeErrors', [])
        duplicated_ids = [error['op']['_id'] for error in write_errors if error['code'] == 11000]

        if duplicated_ids:
            print(f"IDs duplicados encontrados: {duplicated_ids}")
            # Remover documentos duplicados del conjunto original
            documents = [doc for doc in notas if doc['_id'] not in duplicated_ids]
            if documents:  # Verificar si aun hay documentos por insertar
                try:
                    coleccion.insert_many(documents)
                    print("Documentos restantes insertados exitosamente.")
                except errors.BulkWriteError as bwe_inner:
                    print("Error en la insercion en bloque (segunda vez):", bwe_inner.details)
            else:
                print("No hay documentos restantes por insertar despues de filtrar duplicados.")

def capitalizar_centro_id(cadena):
    palabras = cadena.split('-')
    palabras_capitalizadas = [palabra.capitalize() for palabra in palabras]
    return '-'.join(palabras_capitalizadas)

# def obtener_alertas_activas(service, centro_id):
#     if service == "ENVIRO":
#         coleccion = col_enviro_alertas

#     elif service == "NANOOX":
#         coleccion = col_nanoox_alertas
    
#     centro = capitalizar_centro_id(centro_id)
#     # Definir el pipeline de agregacion
#     pipeline = [
#         {
#             '$match': {
#                 'centro_id' : centro,
#                 'fin': None,
#                 'usuario': None,
#                 'fin': {'$exists': True},
#                 'usuario': {'$exists': True}
#             }
#         },
#         {
#             '$project': {
#                 '_id': {'$toString': '$_id'},  # Excluir el campo _id, si no lo quieres en los resultados
#                 # '_id': 0,
#                 'fecha': 1,
#                 'centro_id': 1,
#                 'variable': 1,
#                 'valor': 1,
#                 'alerta': 1,            
#                 'usuario': 1,
#                 'fin': 1
#             }
#         }
#     ]
    
#     # Ejecutar la agregacion
#     resultados = list(coleccion.aggregate(pipeline))
#     return resultados
def convertir_mal_menor(centro_original):
    division = centro_original.split("-")
    division.reverse()
    centro_formateado = "-".join(division)
    return centro_formateado

def obtener_alertas_activas(client, service, centro_id):
    centro = capitalizar_centro_id(centro_id)
    date = datetime.now().strftime("%Y-%m-%d")
    # print(f"alertas activas id {centro_id=}")
    # centro = convertir_mal_menor(centro_id)
    # print(f"alertas activas {centro=}")
    if service == "ENVIRO":
        # coleccion = col_enviro_alertas
        coleccion = client[enviro_database_name].alertas

        pipeline = [
        {
            '$match': {
                'centro_id' : centro,
                'fin': None,
                'usuario': None,
                'fin': {'$exists': True},
                'usuario': {'$exists': True}
            }
        },
        {
            '$project': {
                '_id': {'$toString': '$_id'},  # Excluir el campo _id, si no lo quieres en los resultados
                # '_id': 0,
                'fecha': 1,
                'centro_id': 1,
                'variable': 1,
                'valor': 1,
                'alerta': 1,            
                'usuario': 1,
                'fin': 1, 
                'fin_estado': 1
            }
        }
    ]

    elif service == "NANOOX":
        # print('ENTRAMOS A ALERTAS ACTIVAS NANOOX')
        # coleccion = col_nanoox_alertas
        coleccion = client[nanoox_database_name].alertas
        # centro = convertir_mal_menor(centro)
        centro = capitalizar_centro_id(centro_id)
        pipeline = [
        {
            '$match': {
                'centro_id' : centro,
                'fecha': {"$regex": f"^{date}"},
                'fin_estado': { '$exists': True, '$eq': None },
                'usuario': { '$exists': True, '$eq': None },
                # 'fin_estado': None,
                # 'usuario': None,
                # 'fin_estado': {'$exists': True},
                # 'usuario': {'$exists': True}
            }
        },
        {
            '$project': {
                '_id': {'$toString': '$_id'},  # Excluir el campo _id, si no lo quieres en los resultados
                # '_id': 0,
                'fecha': 1,
                'centro_id': 1,
                'variable': 1,
                'valor': 1,
                'alerta': 1,
                'rango': 1,           
                'usuario': 1,
                'fin': 1,
                'fin_estado': 1
            }
        }
    ]
    
    # Ejecutar la agregacion
    resultados = list(coleccion.aggregate(pipeline))
    return resultados

def obtener_fallas_activas(client, service, centro_id):
    centro = capitalizar_centro_id(centro_id)
    date = datetime.now().strftime("%Y-%m-%d")
    if service == "ENVIRO":
        # coleccion = col_enviro_fallas
        coleccion = client[enviro_database_name].alertas_fallas

    elif service == "NANOOX":
        # coleccion = col_nanoox_fallas
        coleccion = client[nanoox_database_name].alertas_fallas
        centro = capitalizar_centro_id(centro_id)
        # centro = convertir_mal_menor(centro)
    
    # Definir el pipeline de agregacion
    pipeline = [
        {
            '$match': {
                'centro_id' : centro,
                'fecha': {"$regex": f"^{date}"},
                'fin_estado': { '$exists': True, '$eq': None },
                'usuario': { '$exists': True, '$eq': None },
            }
        },
        {
            '$project': {
                '_id': {'$toString': '$_id'},  # Excluir el campo _id, si no lo quieres en los resultados
                # '_id': 0,
                'fecha': 1,
                'centro_id': 1,
                'variable': 1,
                'valor': 1,
                'alerta':1,
                'falla': 1,            
                'usuario': 1,
                'fin': 1,
                'fin_estado': 1
            }
        }
    ]
    
    # Ejecutar la agregacion
    resultados = list(coleccion.aggregate(pipeline))
    return resultados
# def estado_enviro(centro_id):
#     estado_enviro = []
#     print(f'obtener_alertas_activas: {obtener_alertas_activas(centro_id)}')
#     for activa in obtener_alertas_activas(centro_id):
#         print(f'activa: {activa["alerta"]}')
#         estado_enviro.append(activa['alerta'])
#     critico = any(estado == 'C' for estado in estado_enviro)
#     print(f'critico: {critico}')
#     atencion = any(estado == 'A' for estado in estado_enviro)
#     print(f'atencion: {atencion}')
#     if critico:
#         return 'C'
#     elif atencion:
#         return 'A'
#     else:
#         return 'B'
    
def estado_enviro(client, centro_id):
    estado_enviro = []
    # print(f'obtener_alertas_activas: {obtener_alertas_activas(client, "ENVIRO",centro_id)}')
    for activa in obtener_alertas_activas(client, "ENVIRO", centro_id):
        # print(f'activa: {activa["alerta"]}')
        estado_enviro.append(activa['alerta'])
    critico = any(estado == 'C' for estado in estado_enviro)
    # print(f'critico: {critico}')
    atencion = any(estado == 'A' for estado in estado_enviro)
    # print(f'atencion: {atencion}')
    if critico:
        return 'C'
    elif atencion:
        return 'A'
    else:
        return 'B'

def estado_centro(estado_enviro, estado_nanoox):
    estado_centro = [estado_enviro, estado_nanoox]
    critico = any(estado == 'C' for estado in estado_centro)
    atencion = any(estado == 'A' for estado in estado_centro)
    if critico:
        return 'C'
    elif atencion:
        return 'A'
    else:
        return 'B'

# def actualizar_revision_alerta(centro_id, idx, usuario_revision, fecha):
#     bulk_operations = []
#     operation = UpdateOne(
#     {
#         # '_id': ObjectId(idx),
#         '_id': idx,
#         'centro_id' : centro_id
#     },
#         {'$set':
#          {
#              'fin': fecha,
#              'usuario': usuario_revision
#          }
#         },
#         upsert = False
#     )
#     bulk_operations.append(operation)
#     if bulk_operations:
#         col_enviro_alertas.bulk_write(bulk_operations)
#         print(f'Alerta {idx} revisada por usuario {usuario_revision}')

def actualizar_revision_alerta(client, service, centro_id, idx, usuario_revision, fecha):
    bulk_operations = []
    if service == "ENVIRO":
        # coleccion = col_enviro_alertas
        coleccion = client[enviro_database_name].alertas

    elif service == "NANOOX":
        # coleccion = col_nanoox_alertas
        coleccion = client[nanoox_database_name].alertas
    operation = UpdateOne(
    {
        # '_id': ObjectId(idx),
        '_id': idx,
        'centro_id' : centro_id
    },
        {'$set':
         {
             'fin': fecha,
             'usuario': usuario_revision
         }
        },
        upsert = False
    )
    bulk_operations.append(operation)
    if bulk_operations:
        coleccion.bulk_write(bulk_operations)
        print(f'Alerta {idx} revisada por usuario {usuario_revision}')

def actualizar_revision_falla(client, service, centro_id, idx, usuario_revision, fecha):
    bulk_operations = []
    if service == "ENVIRO":
        # coleccion = col_enviro_fallas
        coleccion = client[enviro_database_name].alertas_fallas


    elif service == "NANOOX":
        # coleccion = col_nanoox_fallas
        coleccion = client[nanoox_database_name].alertas_fallas
    
    fin_estado_falla = coleccion.find_one({"_id":idx, "centro_id":centro_id})#['fin_estado']
    print(f"{fin_estado_falla=}")
    if not fin_estado_falla:
        print(f"No se encontro el documento con _id={idx} y centro_id={centro_id}; Probablemente ya fue consolidado.")
        return 

    # if fin_estado_falla:
    operation = UpdateOne(
    {
        # '_id': ObjectId(idx),
        '_id': idx,
        # 'variable': variable,
        'centro_id' : centro_id
    },
        {'$set':
        {
            'fin_estado': fecha,
            'usuario': usuario_revision
        }
        },
        upsert = False
    )          
    bulk_operations = [operation]
    if bulk_operations:
        coleccion.bulk_write(bulk_operations)
        print(f'Falla {idx} en centro {centro_id} revisada por usuario {usuario_revision} el {fecha}')

#Funcion en ITGSM para recepcion y actualizacion de fin estado alerta
def actualizar_fin_estado_alerta(client, service, centro_id, idx, fecha):
    bulk_operations = []
    if service == "ENVIRO":
        # coleccion = col_enviro_alertas
        coleccion = client[enviro_database_name].alertas


    elif service == "NANOOX":
        # coleccion = col_nanoox_alertas
        coleccion = client[nanoox_database_name].alertas
    operation = UpdateOne(
    {
        # '_id': ObjectId(idx),
        '_id': idx,
        'centro_id' : centro_id
    },
        {'$set':
         {
             'fin_estado': fecha,
         }
        },
        upsert = False
    )
    bulk_operations.append(operation)
    if bulk_operations:
        coleccion.bulk_write(bulk_operations)
        print(f'Alerta {idx} actualizada con fin estado a las {fecha} desde Local')

#----------Funciones para obtencion de ESTADOS-------------------#
def obtener_centros_areas_regiones():
    car = list(enviro_metadata_collection.find())[0]
    return car

def obtener_ids(data):
    # Inicializar la lista para almacenar los valores de "_id"
    ids = []
    
    # Recorrer el diccionario y agregar los valores de "_id" a la lista
    for region, region_values in data.items():
        for area, area_values in region_values.items():
            for centro, centro_values in area_values.items():
                if '_id' in centro_values:
                    ids.append(centro_values['_id'])
    
    # Imprimir la lista de valores de "_id"
    # print(f"{ids=}")
    return ids

# def obtener_valores_actuales_centros(data):
#     nuevos_valores = {}
#     #Aqui se debe reemplazar el estado nanoox cuando se tenga
#     # estado_nanoox= None
#     for centro in obtener_ids(data):
#         nuevos_valores[centro] = estado_centro(estado_enviro(centro), None)
#     return (nuevos_valores)

def obtener_valores_actuales_centros(data):
    nuevos_valores = {}
    #Aqui se debe reemplazar el estado nanoox cuando se tenga
    # estado_nanoox= None
    # print(f'obtener_ids(data): {obtener_ids(data)}')
    for centro in obtener_ids(data):
        # print(f'{centro=}')
        # print(f'estado_enviro: {obtener_estado_servicio_centro(centro)["enviro"]}')
        # print(f'estado_nanoox:{obtener_estado_servicio_centro(centro)["nanoox"]}')
        # nuevos_valores[centro] = estado_centro(estado_enviro(centro), None)
        nuevos_valores[centro] = estado_centro(obtener_estado_servicio_centro(centro.lower())['enviro'], obtener_estado_servicio_centro(centro.lower())['nanoox'])
    # print(f'{nuevos_valores=}')
    return (nuevos_valores)

def agrupar_por_region_area(data):
    result = defaultdict(dict)
    
    # Iterar sobre las regiones, areas y centros
    for region, areas in data.items():
        for area, centros in areas.items():
            key = f"{region}-{area}"
            for centro, info in centros.items():
                result[key][info['_id']] = None
    
    # Convertir defaultdict a dict normal
    result = dict(result)
    
    # Imprimir el diccionario resultante
    # print(result)
    return result

def agregar_estado_centros_a_consolidado(agrupacion_por_region_area, dict_estados_centro):
    result = agrupacion_por_region_area
    nuevos_valores = dict_estados_centro
    # Iterar sobre el diccionario resultante y actualizar los valores
    for region_area, centros in result.items():
        for centro_id in centros:
            if centro_id in nuevos_valores:
                result[region_area][centro_id] = nuevos_valores[centro_id]
    
    # Imprimir el diccionario actualizado
    # print(result)
    return result

# Funcion para aplicar las condiciones de actualizacion de valores
def actualizar_valor(valor):
    if 'C' in valor:
        return 'C'
    elif 'A' in valor:
        return 'A'
    else:
        return 'B'
    
def estado_resultante_area_region(consolidado_estados_por_centro):
    # Generar el nuevo diccionario
    nuevo_diccionario = {}
    result = consolidado_estados_por_centro
    
    # Iterar sobre el diccionario resultante
    for region_area, centros in result.items():
        # Concatenar los valores de 'CENTRO-AREA-REGION', reemplazando None por ""
        concatenated_values = ''.join(value if value is not None else 'B' for value in centros.values())
        # Guardar en el nuevo diccionario
        nuevo_diccionario[region_area] = concatenated_values
    
    # Imprimir el nuevo diccionario
    # print(f'estados_colapsados_area_region: {nuevo_diccionario}')
    
    # Aplicar la funcion de actualizacion a los valores del diccionario
    for region_area in nuevo_diccionario:
        nuevo_diccionario[region_area] = actualizar_valor(nuevo_diccionario[region_area])
    
    # Imprimir el diccionario actualizado
    # print(f'estado resultante: {nuevo_diccionario}')
    return (nuevo_diccionario)

def estado_resultante_region(estados_resultante_area_region):
    nuevo_diccionario = estados_resultante_area_region
    # Diccionario resultante agrupado por REGION
    diccionario_agrupado = defaultdict(str)
    
    # Iterar sobre el diccionario 'nuevo_diccionario'
    for region_area, valores_concatenados in nuevo_diccionario.items():
        # Separar la REGION y el AREA
        region, area = region_area.split('-')
        # Concatenar los valores en el diccionario agrupado
        diccionario_agrupado[region] += valores_concatenados
    
    # Convertir defaultdict a dict normal
    diccionario_agrupado = dict(diccionario_agrupado)
    
    # Imprimir el diccionario agrupado
    # print(f'estados_colapsados_region: {diccionario_agrupado}')
    # print(diccionario_agrupado)
    
    # Aplicar la funcion de actualizacion a los valores del diccionario
    for region in diccionario_agrupado:
        diccionario_agrupado[region] = actualizar_valor(diccionario_agrupado[region])
    
    # Imprimir el diccionario actualizado
    return (diccionario_agrupado)

# def obtener_todos_los_estados_empresa():
#     data = obtener_centros_areas_regiones()
#     nuevos_valores = obtener_valores_actuales_centros(data)
#     estados_centros = agregar_estado_centros_a_consolidado(agrupar_por_region_area(data), nuevos_valores)
#     estados_areas = estado_resultante_area_region(estados_centros)
#     estados_regiones = estado_resultante_region(estados_areas)
#     # print(f'estados_centros: {estados_centros}')
#     # print(f'estados_areas: {estados_areas}')
#     # print(f'estados_regiones: {estados_regiones}')
#     consolidado_estados = {"centros": estados_centros, "areas": estados_areas, "regiones": estados_regiones }
#     return consolidado_estados

# def obtener_todos_los_estados_empresa():
#     data = obtener_centros_areas_regiones()
#     nuevos_valores = obtener_valores_actuales_centros(data)
#     estados_centros = agregar_estado_centros_a_consolidado(agrupar_por_region_area(data), nuevos_valores)
#     estados_areas = estado_resultante_area_region(estados_centros)
#     estados_regiones = estado_resultante_region(estados_areas)
#     # print(f'estados_centros: {estados_centros}')
#     # print(f'estados_areas: {estados_areas}')
#     # print(f'estados_regiones: {estados_regiones}')
#     consolidado_estados = {"centros": estados_centros, "areas": estados_areas, "regiones": estados_regiones }
#     return consolidado_estados

def transformar_diccionario(data):
    resultado = {}
    
    for region, areas in data.items():
        resultado[region] = {}
        
        for area, centros in areas.items():
            resultado[region][area] = {}
            
            for centro in centros:
                centro_key = centro.split('-')[0]
                resultado[region][area][centro_key] = {'_id': centro}
                
    return resultado

# def consolidado_empresa_por_usuario_activo(username):
#     print("ENTRANDO A CONSOLIDADO")
#     permisos_usuario = get_accesos_usuario(users_collection,  username)
#     permisos_centros_activos = interseccion_permisos_con_servicios(permisos_usuario,servicios_activos_enviro(ciclos_collection))
#     data = transformar_diccionario(permisos_centros_activos)
#     nuevos_valores = obtener_valores_actuales_centros(data)
#     estados_centros = agregar_estado_centros_a_consolidado(agrupar_por_region_area(data), nuevos_valores)
#     estados_areas = estado_resultante_area_region(estados_centros)
#     estados_regiones = estado_resultante_region(estados_areas)
#     print(f'estados_centros: {estados_centros}')
#     print(f'estados_areas: {estados_areas}')
#     print(f'estados_regiones: {estados_regiones}')
#     consolidado_estados = {"centros": estados_centros, "areas": estados_areas, "regiones": estados_regiones }
#     return consolidado_estados

# def consolidado_empresa_por_usuario_activo(username):
#     print('ENTRANDO A CONSOLIDADOS')
#     permisos_usuario = get_accesos_usuario(users_collection,  username)
#     data = transformar_diccionario(permisos_usuario)
#     print(f'data: {data}')
#     nuevos_valores = obtener_valores_actuales_centros(data)
#     print(f'{nuevos_valores=}')
#     print(f'agrupar por region area: {agrupar_por_region_area(data)}')
#     estados_centros = agregar_estado_centros_a_consolidado(agrupar_por_region_area(data), nuevos_valores)
#     print(f'{estados_centros=}')
#     estados_areas = estado_resultante_area_region(estados_centros)
#     estados_regiones = estado_resultante_region(estados_areas)
#     # print(f'estados_centros: {estados_centros}')
#     # print(f'estados_areas: {estados_areas}')
#     # print(f'estados_regiones: {estados_regiones}')
#     consolidado_estados = {"centros": estados_centros, "areas": estados_areas, "regiones": estados_regiones }
#     return consolidado_estados

def obtener_valores_actuales_centros_por_servicio(page,data):
    nuevos_valores = {}
    print(f'ENTRANDO A ESTADO ACTUAL CENTRO EN PAGINA {page}')
    #Aqui se debe reemplazar el estado nanoox cuando se tenga
    # estado_nanoox= None
    # print(f'obtener_ids(data): {obtener_ids(data)}')
    if page == 'HOME':
        for centro in obtener_ids(data):
            # print(f'{centro=}')
            # print(f'estado_enviro: {obtener_estado_servicio_centro(centro)["enviro"]}')
            # print(f'estado_nanoox:{obtener_estado_servicio_centro(centro)["nanoox"]}')
            # nuevos_valores[centro] = estado_centro(estado_enviro(centro), None)
            nuevos_valores[centro] = estado_centro(obtener_estado_servicio_centro(centro.lower())['enviro'], obtener_estado_servicio_centro(centro.lower())['nanoox'])
            # print(f'{nuevos_valores=} \n')
    elif page == 'ENVIRO':
        for centro in obtener_ids(data):
            nuevos_valores[centro] = estado_centro(obtener_estado_servicio_centro(centro.lower())['enviro'], None)
    elif page == 'NANOOX':
        for centro in obtener_ids(data):
            nuevos_valores[centro] = estado_centro(None, obtener_estado_servicio_centro(centro.lower())['nanoox'])
    # print(f'{nuevos_valores=}')
    return (nuevos_valores)

def obtener_valores_actuales_centros_por_servicio_2(client, page,data, username):
    nuevos_valores = {}
    if page == 'HOME':
        for centro in obtener_ids(data):
            # print(f'{centro=}')
            # print(f'estado_enviro: {obtener_estado_servicio_centro(centro)["enviro"]}')
            # print(f'estado_nanoox:{obtener_estado_servicio_centro(centro)["nanoox"]}')
            # nuevos_valores[centro] = estado_centro(estado_enviro(centro), None)
            nuevos_valores[centro] = estado_centro(obtener_estado_servicio_centro(client, centro.lower())['enviro'], obtener_estado_servicio_centro(client, centro.lower())['nanoox'])
    elif page == 'ENVIRO':
        ciclos_collection = client[user_database_name].ciclos
        for centro in obtener_ids(data):
            # print(f"{centro=}")
            status_servicio_centro=  ciclos_collection.find_one({"centro_id": centro},{"_id":0})['enviro']['status'] & status_servicio_permiso_usuario(client, username, centro, 'enviro')
            if status_servicio_centro:
                nuevos_valores[centro] = estado_centro(obtener_estado_servicio_centro(client, centro.lower())['enviro'], None)
    elif page == 'NANOOX':
        ciclos_collection = client[user_database_name].ciclos
        for centro in obtener_ids(data):
            status_servicio_centro=  ciclos_collection.find_one({"centro_id": centro},{"_id":0})['nanoox']['status'] & status_servicio_permiso_usuario(client, username, centro, 'nanoox')
            if status_servicio_centro:
                nuevos_valores[centro] = estado_centro(None, obtener_estado_servicio_centro(client, centro.lower())['nanoox'])
    # print(f'{nuevos_valores=}')
    return (nuevos_valores)

def clean_none(data):
    if isinstance(data, dict):
        # Recorrer cada clave y valor en el diccionario
        keys_to_delete = []
        for key, value in data.items():
            # Llamada recursiva para limpiar los sub-diccionarios o listas
            cleaned_value = clean_none(value)
            if cleaned_value is None:
                # Marcar clave para eliminacion si el valor es None
                keys_to_delete.append(key)
            else:
                # Actualizar el valor limpiado
                data[key] = cleaned_value
        # Eliminar claves con valor None
        for key in keys_to_delete:
            del data[key]
        # Devolver None si el diccionario esta vacio despues de la limpieza
        if not data:
            return None
    elif isinstance(data, list):
        # Limpiar cada elemento en la lista
        data = [clean_none(item) for item in data if clean_none(item) is not None]
        # Devolver None si la lista esta vacia despues de la limpieza
        if not data:
            return None
    return data

def status_servicio_permiso_usuario(client, username, centro_id, servicio):
    users_collection = client[user_database_name].users
    pipeline = [
        {"$match": {"username": username}},
        {"$project": {
            "permisos": {
                "$objectToArray": "$permisos"
            }
        }},
        {"$unwind": "$permisos"},
        {"$project": {
            "region": "$permisos.k",
            "ciudades": {
                "$objectToArray": "$permisos.v"
            }
        }},
        {"$unwind": "$ciudades"},
        {"$project": {
            "ciudad": "$ciudades.k",
            "centros": {
                "$objectToArray": "$ciudades.v"
            }
        }},
        {"$unwind": "$centros"},
        {"$project": {
            "centro_id": "$centros.k",
            "servicios": {
                "$objectToArray": "$centros.v"
            }
        }},
        {"$unwind": "$servicios"},
        {"$match": {
            "centro_id": centro_id,
            "servicios.k": servicio
        }},
        {"$project": {
            "_id": 0,
            "status": "$servicios.v.status"
        }}
    ]   
    result = list(users_collection.aggregate(pipeline))
    return result[0]['status']

def consolidado_empresa_por_usuario_activo_y_servicio(username, page):
    print('ENTRANDO A CONSOLIDADOS ACTUALIZADO')
    permisos_usuario = get_accesos_usuario(users_collection,  username) 
    # print(f"permisos_usuario_orig = {permisos_usuario}")
    # if page == 'ENVIRO':
        # permisos_usuario = interseccion_permisos_con_servicios(permisos_usuario,servicios_activos_enviro(ciclos_collection))
    # elif page == 'NANOOX':
        # permisos_usuario = interseccion_permisos_con_servicios(permisos_usuario,servicios_activos_nanoox(ciclos_collection))
    # print(f"{permisos_usuario=}")
    # interseccion_permisos_con_servicios(permisos_usuario,servicios_activos_enviro(ciclos_collection))
    data = transformar_diccionario(permisos_usuario)
    print(f'data: {data}')
    nuevos_valores = obtener_valores_actuales_centros_por_servicio(page, data)
    # print(f'{nuevos_valores=}')
    # print(f'agrupar por region area: {agrupar_por_region_area(data)}')
    estados_centros = agregar_estado_centros_a_consolidado(agrupar_por_region_area(data), nuevos_valores)
    # print(f'{estados_centros=}')
    estados_areas = estado_resultante_area_region(estados_centros)
    estados_regiones = estado_resultante_region(estados_areas)
    # print(f'estados_centros: {estados_centros}')
    # print(f'estados_areas: {estados_areas}')
    # print(f'estados_regiones: {estados_regiones}')
    consolidado_estados = {"centros": estados_centros, "areas": estados_areas, "regiones": estados_regiones }
    return consolidado_estados


def consolidado_empresa_por_usuario_activo_y_servicio_2(client, username, page):
    print('ENTRANDO A CONSOLIDADOS')
    users_collection = client[user_database_name].users
    if page == 'HOME':
        permisos_usuario = get_accesos_usuario(users_collection,  username) #Obtiene Permisos de Usuario
    else:
        # permisos_usuario = verificar_permiso_vacio(procesar_permisos(list(users_collection.find({'username':username}))[0]))
        permisos_usuario = procesar_permisos(client, list(users_collection.find({'username':username}))[0])
    # print(f"{permisos_usuario = }")
    data = transformar_diccionario(permisos_usuario) #Retorna diccionario anidado {REGION:{AREA:{CENTRO}}}
    # print(f'data: {data}')
    # nuevos_valores = obtener_valores_actuales_centros(data)
    # antiguos_valores = obtener_valores_actuales_centros_por_servicio(page, data)
    # print(f'{antiguos_valores=}')
    nuevos_valores = obtener_valores_actuales_centros_por_servicio_2(client, page, data, username)
    # print(f'{nuevos_valores=}')
    # print(f'agrupar por region area: {agrupar_por_region_area(data)}')
    # estados_centros = agregar_estado_centros_a_consolidado(agrupar_por_region_area(data), nuevos_valores)
    estados_centros = clean_none(agregar_estado_centros_a_consolidado(agrupar_por_region_area(data), nuevos_valores))
    print(f'{estados_centros=}')
    estados_areas = estado_resultante_area_region(estados_centros)
    estados_regiones = estado_resultante_region(estados_areas)
    # print(f'estados_centros: {estados_centros}')
    # print(f'estados_areas: {estados_areas}')
    # print(f'estados_regiones: {estados_regiones}')
    consolidado_estados = {"centros": estados_centros, "areas": estados_areas, "regiones": estados_regiones }
    return consolidado_estados


def obtener_diferencia(fecha_inicio_str, fecha_fin_str):
    # Definir el formato de fecha
    formato = "%Y-%m-%d-%H:%M:%S"
    
    # Convertir las cadenas de fecha a objetos datetime
    fecha_inicio = datetime.strptime(fecha_inicio_str, formato)
    fecha_fin = datetime.strptime(fecha_fin_str, formato)
    
    # Calcular la diferencia
    diferencia = fecha_fin - fecha_inicio
        # Convertir la diferencia en un string en el formato deseado
    dias, segundos = diferencia.days, diferencia.seconds
    horas = segundos // 3600
    minutos = (segundos % 3600) // 60
    segundos = segundos % 60
    
    return f"{dias}d-{horas}h-{minutos}m-{segundos}s"

def obtener_status_servicio(client, centro_id, servicio):
    # Buscar el documento correspondiente por 'centro_id'
    ciclos_collection = client[user_database_name].ciclos
    documento = ciclos_collection.find_one({'centro_id': centro_id})
    
    if documento and servicio in documento:
        return documento[servicio].get('status', False)
    
    # Si el documento o el servicio no existe, devolver False
    return False

def obtener_estado_servicio_centro(client, centro_id):
    """
    Retorna el estado B, A o C de un centro en especifico desde la coleccion user.estados
    """
    estado_centros_collection = client[user_database_name].estados
    # print(f"centro_id 2: {centro_id}")
    # print(f" obtener_estado_servicio_centro 2: {estado_centros_collection.find_one({'centro_id':centro_id},{'_id':0})}")
    estado_servicios = estado_centros_collection.find_one({'centro_id':centro_id},{'_id':0})
    # print(f'{estado_servicios=}')
    if estado_servicios:
        return estado_servicios
    else:
        return "B"

# def estado_enviro_servicios(centro_id):
#     estado_enviro = []
#     for activa in obtener_alertas_activas(centro_id):
#         estado_enviro.append(activa["alerta"])
#     # print(f'estado_enviro: {estado_enviro}')
#     critico = any(estado == 'C' for estado in estado_enviro)
#     atencion = any(estado == 'A' for estado in estado_enviro)
#     conteo_c = estado_enviro.count('C')
#     conteo_a = estado_enviro.count('A')
#     estado_servicio = obtener_status_servicio(centro_id.lower(), "enviro")
#     # print(f'count_a: {estado_enviro}')
#     if critico:
#         return {'status' : 'C', 'conteo_c': conteo_c, 'conteo_a': conteo_a, 'status_service': estado_servicio}
#     elif atencion:
#         return {'status' : 'A', 'conteo_c': conteo_c, 'conteo_a': conteo_a, 'status_service': estado_servicio}
#     else:
#         return {'status' : 'B', 'conteo_c': conteo_c, 'conteo_a': conteo_a, 'status_service': estado_servicio}
# def estado_nanoox_servicios(centro_id):
#     estado_servicio = obtener_status_servicio(centro_id.lower(), "nanoox")
#     return {'status' : 'B', 'conteo_c': 0, 'conteo_a': 0, 'status_service': estado_servicio}
#Estado servicio segun estado variables centro
def estado_enviro_servicios(client, centro_id):

    estado_enviro = []
    # print(f'obtener_alertas_activas en enviro 2: {obtener_alertas_activas(client, "ENVIRO",centro_id)}')
    for activa in obtener_alertas_activas(client, "ENVIRO",centro_id):
        estado_enviro.append(activa["alerta"])
    conteo_c = estado_enviro.count('C')
    conteo_a = estado_enviro.count('A')
    # print(f'obtener_estado_servicio_centro 2: {obtener_estado_servicio_centro(centro_id.lower())["enviro"]}')
    estado_servicio = obtener_status_servicio(client, centro_id.lower(), "enviro")
    estado = obtener_estado_servicio_centro(client, centro_id)['enviro']
    return {'status' : estado, 'conteo_c': conteo_c, 'conteo_a': conteo_a, 'status_service': estado_servicio}
def estado_nanoox_servicios(client, centro_id):
    estado_nanoox = []
    for activa in obtener_alertas_activas(client, "NANOOX",centro_id):
        estado_nanoox.append(activa["alerta"])
    conteo_c = estado_nanoox.count('C')
    conteo_a = estado_nanoox.count('A')
    estado_servicio = obtener_status_servicio(client, centro_id.lower(), "nanoox")
    estado = obtener_estado_servicio_centro(client, centro_id)['nanoox']
    return {'status' : estado, 'conteo_c': conteo_c, 'conteo_a': conteo_a, 'status_service': estado_servicio}
def estado_pss_servicios(client, centro_id):
    estado_servicio = obtener_status_servicio(client, centro_id.lower(), "pss")
    return {'status' : 'B', 'conteo_c': 0, 'conteo_a': 0, 'status_service': estado_servicio}
def estado_scapp_servicios(client, centro_id):
    estado_servicio = obtener_status_servicio(client, centro_id.lower(), "scapp")
    return {'status' : 'B', 'conteo_c': 0, 'conteo_a': 0, 'status_service': estado_servicio}

# def obtener_servicios_por_region_y_area(area, region):
#     result = enviro_metadata_collection.find(
#         {},
#         {
#             # f"Ambiental.{region}.{area}": 1,
#             f"{region}.{area}": 1,
#             "_id": 0
#         }
#     )

#     # Convertir el cursor a una lista de documentos
#     documentos = list(result)
#     # print(f'obtener_servicios_por_region_y_area: {documentos}')
#     return documentos

def capitalize_first_letter(field):
    return {
        '$concat': [
            {'$toUpper': {'$substrCP': [field, 0, 1]}},  # Convertir la primera letra en mayuscula
            {'$substrCP': [field, 1, {'$strLenCP': field}]},  # Mantener el resto en minuscula
        ]
    }

def obtener_servicios_por_region_y_area(client, username):
    collection = client[user_database_name].users
    # Pipeline de agregacion modificado
    pipeline = [
        {'$match': {'username': username}},  # Filtrar por nombre de usuario
        {'$project': {
            'permisos': 1
        }},
        {'$replaceWith': {'$arrayToObject': {
            '$map': {
                'input': {'$objectToArray': '$permisos'},
                'as': 'region',
                'in': {
                    'k': capitalize_first_letter('$$region.k'),  # Capitalizar solo la primera letra de la region
                    'v': {'$arrayToObject': {
                        '$map': {
                            'input': {'$objectToArray': '$$region.v'},
                            'as': 'area',
                            'in': {
                                'k': capitalize_first_letter('$$area.k'),  # Capitalizar solo la primera letra del area
                                'v': {'$arrayToObject': {
                                    '$map': {
                                        'input': {'$objectToArray': '$$area.v'},
                                        'as': 'centro',
                                        'in': {
                                            'k': capitalize_first_letter({
                                                '$arrayElemAt': [
                                                    {'$split': ['$$centro.k', '-']}, 0
                                                ]
                                            }),  # Capitalizar la primera letra del nombre del centro
                                            'v': {'_id': {
                                                '$concat': [
                                                    capitalize_first_letter({
                                                        '$arrayElemAt': [
                                                            {'$split': ['$$centro.k', '-']}, 0
                                                        ]
                                                    }),  # Capitalizar la primera letra del centro
                                                    '-',
                                                    capitalize_first_letter('$$area.k'),  # Capitalizar la primera letra del area
                                                    '-',
                                                    capitalize_first_letter('$$region.k')  # Capitalizar la primera letra de la region
                                                ]
                                            }}
                                        }
                                    }
                                }}
                            }
                        }
                    }}
                }
            }
        }}}
    ]

    # Ejecutar la agregacion
    resultado = list(collection.aggregate(pipeline))

    # Devolver el diccionario estructurado
    if resultado:
        return resultado[0]
    return {}

# def estados_servicios_region_area(region, area):
#     print(f'ENTRANDO A ESTADO SERVICIOS REGION AREA')
#     id_centros = obtener_ids(obtener_servicios_por_region_y_area(area, region)[0])
#     print(f'{id_centros=}')
#     id_centros = [centro.lower() for centro in id_centros]
#     print(f'centros-lower{id_centros}')
#     dict_centros = {}
#     for centro in id_centros:
#         enviro_estado = estado_enviro_servicios(centro)
#         print(f'{enviro_estado=}')
#         nanoox_estado = estado_nanoox_servicios(centro)
#         print(f'{nanoox_estado=}')
#         scapp_estado = estado_scapp_servicios(centro)
#         pss_estado = estado_pss_servicios(centro)
#         dict_centros[centro] = {
#             'enviro': enviro_estado,
#             'nanoox': nanoox_estado,
#             'scapp': scapp_estado,
#             'pss': pss_estado
#         }
#         # dict = {centro: {'enviro': estado_enviro_servicios(centro), 'nanoox': estado_nanoox_servicios(centro), 'scapp': estado_scapp_servicios(centro), 'pss':estado_pss_servicios(centro)}}
#     return dict_centros 

def format_all_keys(d):
    # Crear un nuevo diccionario para almacenar las claves formateadas
    new_dict = {}
    
    # Iterar sobre cada clave en el diccionario original
    for key in d:
        # Formatear la clave para que tenga mayusculas en cada palabra y reemplazar guiones por espacios
        formatted_key = key.replace('-', ' ').title().replace(' ', '-')
        
        # Añadir la clave formateada y su valor correspondiente al nuevo diccionario
        new_dict[formatted_key] = d[key]
    
    return new_dict

# def estados_servicios_region_area_2(region, area):
def estados_servicios_region_area_2(client, username):
    # print(f'-----------ENTRANDO A ESTADO SERVICIOS REGION AREA 2!-------')
    # id_centros = obtener_ids(obtener_servicios_por_region_y_area(area, region)[0])
    id_centros = obtener_ids(obtener_servicios_por_region_y_area(client, username))
    # print(f'{id_centros=}')
    # id_centros = [centro.lower() for centro in id_centros]
    # print(f'centros-lower{id_centros}')
    dict_centros = {}
    for centro in id_centros:
        # print(f'centro3: {centro}')
        centro = centro.lower()
        # print(f'centro4: {centro}')
        enviro_estado = estado_enviro_servicios(client, centro)
        nanoox_estado = estado_nanoox_servicios(client, centro)
        scapp_estado = estado_scapp_servicios(client, centro)
        pss_estado = estado_pss_servicios(client, centro)
        dict_centros[centro] = {
            'enviro': enviro_estado,
            'nanoox': nanoox_estado,
            'scapp': scapp_estado,
            'pss': pss_estado
        }
        
        # print(f"Estados de servicios: \nNanoox: {nanoox_estado} \nEnviro{enviro_estado}")
        # dict = {centro: {'enviro': estado_enviro_servicios(centro), 'nanoox': estado_nanoox_servicios(centro), 'scapp': estado_scapp_servicios(centro), 'pss':estado_pss_servicios(centro)}}
    # print(f"{dict_centros=}")
    dict_resultante = format_all_keys(dict_centros)
    # print(f"{dict_resultante=}")
    return dict_resultante 

# def actualizar_consolidado_alertas_revisadas(idx):
#     document = col_enviro_alertas.find_one({"_id": idx})
#     if document:
#         # Extraer la fecha y formatearla a "Y-M-D"
#         fecha_str = document['fecha'].split('-')[0:3]
#         fecha = "-".join(fecha_str)
        
#         # Construir el filtro para encontrar el documento
#         filtro = {
#             "fecha": fecha,
#             "centro_id": document['centro_id']
#             # "_id": {"$ne": document['_id']}
#         }
        
#         # Definir el documento que se va a insertar o actualizar
#         actualizacion = {
#             "$setOnInsert": {
#                 "fecha": fecha,
#                 "centro_id": document['centro_id'],
#                 "variables": {}
#             }
#         }
        
#         # Realizar la operacion de upsert
#         col_enviro_alertas.update_one(filtro, actualizacion, upsert = True)
    
#         # Ahora actualizamos la variable especifica
#         actualizacion_variable = {
#             "$push": {
#                 f"variables.{document['variable']}": {
#                     "inicio": document['fecha'],
#                     "valor": document['valor'],
#                     "alerta": document['alerta'],
#                     "usuario": document['usuario'],
#                     "fin": document['fin'],
#                     "duracion" : obtener_diferencia(document['fecha'], document['fin'])
#                 }
#             }
#         }    
#         # Actualizar la variable especifica en el documento
#         resultado = col_enviro_alertas.update_one(filtro, actualizacion_variable)
#         print(f' Consolidado de Alertas actualizado con alerta {idx}')
#         if resultado.matched_count > 0:
#             if resultado.modified_count > 0:
#                 print("El documento se actualizo correctamente.")
#                 actualizacion = True
#             else:
#                 print("El documento coincidio con el filtro, pero no se modifico.")
#                 actualizacion = False
#         else:
#             print("No se encontro ningun documento que coincidiera con el filtro.")
#             actualizacion = False

#         if actualizacion:
#              col_enviro_alertas.delete_one({"_id": idx})
#              print(f"La alerta {idx} se ha agregado al consolidado y removido de alertas")
#     else:
#         print(f' No se encontro alerta asociada al id {idx}')        
def check_alerta_incompleta(col_enviro_alertas, idx):
    # Crear la query
    query = {
        '$and': [
            {'_id': ObjectId(idx)},
            {'fin': {'$exists': True}},
            {'usuario': {'$exists': True}},
            {'fin_estado': {'$exists': True}},
            {
                '$or': [
                    {'fin': None},
                    {'usuario': None},
                    {'fin_estado': None}
                ]
            }
        ]
    }
    
    # Ejecutar la query
    resultados = list(col_enviro_alertas.find(query))
    if resultados:
        return False
    else:
        return True

# def actualizar_consolidado_alertas_revisadas(service, idx):
#     if service == "ENVIRO":
#         coleccion = col_enviro_alertas

#     elif service == "NANOOX":
#         coleccion = col_nanoox_alertas

#     document = coleccion.find_one({"_id": idx})
#     if document:
#         # Extraer la fecha y formatearla a "Y-M-D"
#         fecha_str = document['fecha'].split('-')[0:3]
#         fecha = "-".join(fecha_str)
        
#         # Construir el filtro para encontrar el documento
#         filtro = {
#             "fecha": fecha,
#             "centro_id": document['centro_id']
#             # "_id": {"$ne": document['_id']}
#         }
        
#         # Definir el documento que se va a insertar o actualizar
#         actualizacion = {
#             "$setOnInsert": {
#                 "fecha": fecha,
#                 "centro_id": document['centro_id'],
#                 "variables": {}
#             }
#         }
        
#         # Realizar la operacion de upsert
#         coleccion.update_one(filtro, actualizacion, upsert = True)
    
#         # Ahora actualizamos la variable especifica
#         actualizacion_variable = {
#             "$push": {
#                 f"variables.{document['variable']}": {
#                     "inicio": document['fecha'],
#                     "valor": document['valor'],
#                     "alerta": document['alerta'],
#                     "usuario": document['usuario'],
#                     "fin": document['fin'],
#                     "duracion" : obtener_diferencia(document['fecha'], document['fin'])
#                 }
#             }
#         }    
#         # Actualizar la variable especifica en el documento
#         resultado = coleccion.update_one(filtro, actualizacion_variable)
#         print(f' Consolidado de Alertas actualizado con alerta {idx}')
#         if resultado.matched_count > 0:
#             if resultado.modified_count > 0:
#                 print("El documento se actualizo correctamente.")
#                 actualizacion = True
#             else:
#                 print("El documento coincidio con el filtro, pero no se modifico.")
#                 actualizacion = False
#         else:
#             print("No se encontro ningun documento que coincidiera con el filtro.")
#             actualizacion = False

#         if actualizacion:
#              coleccion.delete_one({"_id": idx})
#              print(f"La alerta {idx} se ha agregado al consolidado y removido de alertas")
#     else:
#         print(f' No se encontro alerta asociada al id {idx}')  
def actualizar_consolidado_alertas_revisadas(client, service, idx):
    if service == "ENVIRO":
        # coleccion = col_enviro_alertas
        coleccion = client[enviro_database_name].alertas


    elif service == "NANOOX":
        # coleccion = col_nanoox_alertas
        coleccion = client[nanoox_database_name].alertas

    document = coleccion.find_one({"_id": idx})
    if document:
        # Extraer la fecha y formatearla a "Y-M-D"
        fecha_str = document['fecha'].split('-')[0:3]
        fecha = "-".join(fecha_str)
        
        # Construir el filtro para encontrar el documento
        filtro = {
            "fecha": fecha,
            "centro_id": document['centro_id']
            # "_id": {"$ne": document['_id']}
        }
        
        # Definir el documento que se va a insertar o actualizar
        actualizacion = {
            "$setOnInsert": {
                "fecha": fecha,
                "centro_id": document['centro_id'],
                "variables": {}
            }
        }
        
        # Realizar la operacion de upsert
        coleccion.update_one(filtro, actualizacion, upsert = True)
    
        # Ahora actualizamos la variable especifica
        actualizacion_variable = {
            "$push": {
                f"variables.{document['variable']}": {
                    "inicio": document['fecha'],
                    "valor": document['valor'],
                    "alerta": document['alerta'],
                    "usuario": document['usuario'],
                    "fin": document['fin'],
                    "fin_estado" : document['fin_estado'],
                    "duracion" : obtener_diferencia(document['fecha'], document['fin'])
                }
            }
        }    
        # Actualizar la variable especifica en el documento
        resultado = coleccion.update_one(filtro, actualizacion_variable)
        print(f' Consolidado de Alertas actualizado con alerta {idx}')
        if resultado.matched_count > 0:
            if resultado.modified_count > 0:
                print("El documento se actualizo correctamente.")
                actualizacion = True
            else:
                print("El documento coincidio con el filtro, pero no se modifico.")
                actualizacion = False
        else:
            print("No se encontro ningun documento que coincidiera con el filtro.")
            actualizacion = False

        if actualizacion:
             coleccion.delete_one({"_id": idx})
             print(f"La alerta {idx} se ha agregado al consolidado y removido de alertas")
    else:
        print(f' No se encontro alerta asociada al id {idx}')  

def actualizar_consolidado_fallas_revisadas(client, service,  idx): #centro_id,
    if service == "ENVIRO":
        # coleccion = col_enviro_fallas
        coleccion = client[enviro_database_name].alertas_fallas


    elif service == "NANOOX":
        # coleccion = col_nanoox_fallas
        coleccion = client[nanoox_database_name].alertas_fallas


    query = {"_id": idx} #"centro_id": centro_id,
    # document = coleccion.find_one({"_id": idx})
    document = coleccion.find_one(query)
    if document:
        # Extraer la fecha y formatearla a "Y-M-D"
        fecha_str = document['fecha'].split('-')[0:3]
        fecha = "-".join(fecha_str)
        
        # Construir el filtro para encontrar el documento
        filtro = {
            "fecha": fecha,
            "centro_id": document['centro_id']
            # "_id": {"$ne": document['_id']}
        }
        
        # Definir el documento que se va a insertar o actualizar
        actualizacion = {
            "$setOnInsert": {
                "fecha": fecha,
                "centro_id": document['centro_id'],
                "variables": {}
            }
        }
        
        # Realizar la operacion de upsert
        coleccion.update_one(filtro, actualizacion, upsert = True)
    
        # Ahora actualizamos la variable especifica
        actualizacion_variable = {
            "$push": {
                f"variables.{document['variable']}": {
                    "inicio": document['fecha'],
                    "valor": document['valor'],
                    "alerta": document['alerta'],
                    "usuario": document['usuario'],
                    "fin": document['fin'],
                    "fin_estado" : document['fin_estado'],
                    "duracion" : obtener_diferencia(document['fecha'], document['fin_estado'])
                }
            }
        }    
        # Actualizar la variable especifica en el documento
        resultado = coleccion.update_one(filtro, actualizacion_variable)
        print(f' Consolidado de Fallas actualizado con alerta {idx} ')
        if resultado.matched_count > 0:
            if resultado.modified_count > 0:
                print("El documento se actualizo correctamente.")
                actualizacion = True
            else:
                print("El documento coincidio con el filtro, pero no se modifico.")
                actualizacion = False
        else:
            print("No se encontro ningun documento que coincidiera con el filtro.")
            actualizacion = False

        if actualizacion:
             idx = document["_id"]
             coleccion.delete_one({"_id": idx})
             print(f"La falla {idx} se ha agregado al consolidado y removido de fallas")
    else:
        print(f' No se encontro falla asociada a la falla {idx}')

def capitalize_first_keys(d, depth=1, max_depth=3):
    if isinstance(d, dict):
        new_dict = {}
        for key, value in d.items():
            if depth <= max_depth:
                new_key = key.title()
            else:
                new_key = key
            new_dict[new_key] = capitalize_first_keys(value, depth + 1, max_depth)
        return new_dict
    elif isinstance(d, list):
        return [capitalize_first_keys(item, depth, max_depth) for item in d]
    else:
        return d


def procesar_permisos(client, permisos_usuarios):
    """
    REaliza el cruce entre los permisos que posee el usuario con los permisos actuales almacenados en la coleccion ciclos,
    y construye un nuevo diccionario con el “permiso efectivo” (status) para cada servicio de cada centro, segun {REGION{AREA{CENTRO}}
    """
    ciclos_collection = client[user_database_name].ciclos 
    # Inicializar el diccionario resultante
    result_dict = {}
    # print(f'input: {permisos_usuarios}')
    for region in permisos_usuarios['permisos']:
        result_dict[region] = {}
        for area in permisos_usuarios['permisos'][region]:
            result_dict[region][area] = {}
            for centro in permisos_usuarios['permisos'][region][area]:
                # print(f'centro find: {centro.lower()}')
                info_centro_ciclo = list(ciclos_collection.find({"centro_id": centro.lower()}))
                if info_centro_ciclo:
                    result_dict[region][area][centro] = {}
                    for servicio in permisos_usuarios['permisos'][region][area][centro]:
                        status = permisos_usuarios['permisos'][region][area][centro][servicio]['status']
                        if servicio == "enviro":
                            status_centro_ciclos = info_centro_ciclo[0]['enviro']['status']
                            comparacion_enviro = status and status_centro_ciclos
                            result_dict[region][area][centro][servicio] = {'status': comparacion_enviro}
                        elif servicio == "nanoox":
                            status_centro_ciclos = info_centro_ciclo[0]['nanoox']['status']
                            comparacion_nanoox = status and status_centro_ciclos
                            result_dict[region][area][centro][servicio] = {'status': comparacion_nanoox}
                        elif servicio == "scapp":
                            status_centro_ciclos = info_centro_ciclo[0]['scapp']['status']
                            comparacion_scapp = status and status_centro_ciclos
                            result_dict[region][area][centro][servicio] = {'status': comparacion_scapp}
                        elif servicio == "pss":
                            status_centro_ciclos = info_centro_ciclo[0]['pss']['status']
                            comparacion_pss = status and status_centro_ciclos
                            result_dict[region][area][centro][servicio] = {'status': comparacion_pss}
                else:
                    result_dict[region][area][centro] = []

    # Retornar el diccionario resultante
    return result_dict
def procesar_permisos_de_usuario(client, usuario):
    users_collection = client[user_database_name].users
    # user = list(db.usars.find({'username':usuario}))
    user = list(users_collection.find({'username':usuario}))
    # print(f'usuario_para_permisos: {user}')
    if user:
        usuario_capitalizado = user[0]
        usuario_capitalizado['permisos'] =  capitalize_first_keys(usuario_capitalizado['permisos'])
        permisos_procesados = procesar_permisos(client, usuario_capitalizado)
        # print(f'final: {permisos_procesados}\n')
        return permisos_procesados
    else:
        return None
def update_status_service(original_dict1, dict2):
    new_dict = {}
    
    for key1, subdict1 in original_dict1.items():
        for key2, subdict2 in subdict1.items():
            # Ya no convertimos key2 a minusculas porque las llaves ya estan en el formato correcto
            for key_region, region_data in dict2.items():
                for key_area, area_data in region_data.items():
                    if key2 in area_data:
                        if key1 not in new_dict:
                            new_dict[key1] = {}
                        new_dict[key1][key2] = copy.deepcopy(subdict2)
                        for service, values in new_dict[key1][key2].items():
                            if service in area_data[key2]:
                                values['status_service'] = area_data[key2][service]['status']
    
    return new_dict
# def actualizar_umbral(coleccion, centro_id, variable, nuevos_valores):
    
#     # Filtro para encontrar el documento
#     filtro = {
#         "centro_id": centro_id,
#         "variable": variable
#     }
    
#     # Actualizacion de los campos
#     actualizacion = {
#         "$set": nuevos_valores
#     }
    
#     # Actualizacion del documento
#     resultado = coleccion.update_one(filtro, actualizacion)

#     if resultado.modified_count > 0:
#         return True
#     else:
#         return False
def actualizar_umbral(collection, centro_id, variable, nuevos_valores):
    operaciones = [
        UpdateOne(
            {"centro_id": centro_id, "variable": variable},
            {"$set": nuevos_valores},
            upsert=True
        )
    ]

    try:
        resultado = collection.bulk_write(operaciones, ordered=False)
        if resultado.modified_count > 0:
            respuesta = True
        else:
            respuesta = False
        return respuesta
    except BulkWriteError as bwe:
        print(f"Error de escritura en bulk: {bwe.details}")
        respuesta = False
        return respuesta    

# def guardar_umbrales_activos(service, umbrales):
#     if service == "ENVIRO":
#         coleccion = col_enviro_umbrales

#     elif service == "NANOOX":
#         coleccion = col_nanoox_umbrales

#     print(f'umbrales_desde_local: {umbrales}')
#     try:
#         coleccion.insert_many(umbrales)
#         print("Documentos insertados exitosamente.")
#     except errors.BulkWriteError as bwe:
#         print("Error en la insercion en bloque, manejando duplicados...")
#         # Filtrar documentos duplicados
#         error_details = bwe.details
#         write_errors = error_details.get('writeErrors', [])
#         duplicated_ids = [error['op']['_id'] for error in write_errors if error['code'] == 11000]

#         if duplicated_ids:
#             print(f"IDs duplicados encontrados: {duplicated_ids}")
#             # Remover documentos duplicados del conjunto original
#             documents = [doc for doc in umbrales if doc['_id'] not in duplicated_ids]
#             if documents:  # Verificar si aun hay documentos por insertar
#                 try:
#                     coleccion.insert_many(documents)
#                     print("Documentos restantes insertados exitosamente.")
#                 except errors.BulkWriteError as bwe_inner:
#                     print("Error en la insercion en bloque (segunda vez):", bwe_inner.details)
#             else:
#                 print("No hay documentos restantes por insertar despues de filtrar duplicados.")


def guardar_umbrales_activos(client, service, umbrales):
    operations = []
    print(f"{service=}")
    print(f"{nanoox_database_name=}")
    if service == "ENVIRO":
        # coleccion = col_enviro_umbrales
        coleccion = client[enviro_database_name].umbrales


    elif service == "NANOOX":
        # coleccion = col_nanoox_umbrales
        coleccion = client[nanoox_database_name].umbrales


    print(f'umbrales_desde_local: {umbrales}') 
    for umbral in umbrales:
        print(f"{umbral=}")
        print(f'{umbral["centro_id"]=}')
        print(f'{umbral["variable"]=}')
        filter = {"centro_id": umbral["centro_id"], "variable": umbral["variable"]}
        update = {"$set": umbral}
        operations.append(UpdateOne(filter, update, upsert=True))
    
    try:
        # Realizar la operacion de escritura en bloque
        result = coleccion.bulk_write(operations)
        print("Documentos restantes insertados exitosamente.")
    except errors.BulkWriteError as bwe:
        print(f"Error en la operacion de escritura en bloque: {bwe.details}")
    except errors.PyMongoError as e:
        print(f"Error en PyMongo: {e}")


# def guardar_umbrales_nanoox(client, umbrales):
    
def guardar_umbrales_nanoox(client, umbrales, collection_name="umbrales", new_version = False):
    """
    Guarda los umbrales en una colección de series de tiempo.
    Si la colección no existe, la crea como time series.
    """

    if new_version:
        collection_name="umbrales_v2"
        try:
            centro_id = umbrales.get("centro")
            nuevos_datos = umbrales.get("data")

            if not nuevos_datos or not isinstance(nuevos_datos, dict):
                print(f"Datos de umbrales inválidos: {nuevos_datos}")
                return

            
            # --- Verificar y crear colección como time series si no existe ---
            db = client[nanoox_database_name]
            if collection_name not in db.list_collection_names():
                db.create_collection(
                    collection_name,
                    timeseries={
                        "timeField": "timestamp",   # campo obligatorio
                        "metaField": "centro_id",   # opcional: clave de agrupación
                        "granularity": "seconds"    # depende de la frecuencia de datos
                    }
                )
                print(f"Colección {collection_name} creada como time series")

            umbrales_col = db[collection_name]

            # Timestamp en UTC
            fecha_actual = datetime.utcnow()

            # Buscar el último dato guardado para ese centro
            existing_doc = umbrales_col.find_one(
                {"centro_id": centro_id},
                sort=[("timestamp", -1)]
            )

            if existing_doc:
                # Extraer solo los campos relevantes del último doc
                ultimo_dato = {
                    "setpoin_off": existing_doc.get("setpoin_off"),
                    "setpoin_on": existing_doc.get("setpoin_on")
                }

                print(ultimo_dato, nuevos_datos )
                # Comparar con los nuevos valores recibidos
                if ultimo_dato == nuevos_datos:
                    print(f"{centro_id}: Umbrales sin cambios, no se inserta nuevo documento.")
                    return


            # Insertar nuevo documento en la serie de tiempo
            doc = {
                "centro_id": centro_id,
                **nuevos_datos, 
                "timestamp": fecha_actual
            }

            umbrales_col.insert_one(doc)
            print(f"Nuevo umbral insertado en {collection_name} para centro {centro_id}")

        except Exception as e:
            print(f"Error al guardar umbrales en centro {centro_id if 'centro_id' in locals() else '?'}: {e}")

    else:
        collection_name="umbrales"
        centro = umbrales.get('centro')
        nuevos_datos = umbrales.get('data')
        fecha = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        col_nanoox_umbrales= client[nanoox_database_name].umbrales
        # Buscar el documento existente por 'nombre'
        existing_doc = col_nanoox_umbrales.find_one({'centro_id': centro})
        print(f"Guardar umbrales Nanoox: {centro=} {nuevos_datos=}")

        if existing_doc:
            # Verificar si los datos son diferentes al existente
            if existing_doc.get('datos')[-1][:4] != nuevos_datos[:4]:
                nuevos_datos.append(fecha)
                # Preparar el array completo de datos para insertar
                # array_completo = [existing_doc['datos'][0]] + nuevos_datos

                # Preparar la operacion de actualizacion (upsert)
                update_operation = UpdateOne(
                    {'_id': existing_doc['_id']},
                    {'$push': {'datos': nuevos_datos}}
                )

                # Ejecutar la operacion de actualizacion
                result = col_nanoox_umbrales.bulk_write([update_operation])

                # Imprimir resultado
                print(f"Upsert realizado. Documentos modificados: {result.modified_count}, documentos insertados: {result.upserted_count}")
            else:
                print(f"{centro}: Datos de Umbrales sin cambios, no se actualiza en bd.")
        else:
            nuevos_datos.append(fecha)
            # Si no hay documento existente, insertar uno nuevo con todos los datos
            col_nanoox_umbrales.insert_one({'centro_id': centro, 'datos': [nuevos_datos]})
            print("Nuevo documento insertado.")


    

def get_umbrales_nanoox(db_nanoox, centro_id,  collection_name="umbrales", new_version = False):
    try:
        # Conexion a la base de datos MongoDB
        # collection = col_nanoox_umbrales
        
        # Buscar el último dato guardado para ese centro

        if new_version:
            collection_name = "umbrales_v2"

            collection = db_nanoox[collection_name]
            existing_doc = collection.find_one(
                {"centro_id": centro_id},
                sort=[("timestamp", -1)]
            )

            print(f"{existing_doc=}")

            if existing_doc:
                # Extraer solo los campos relevantes del último doc
                umbrales_data = {
                    "setpoin_off": existing_doc.get("setpoin_off"),
                    "setpoin_on": existing_doc.get("setpoin_on")
                }
                return umbrales_data

        else:
            collection = db_nanoox.umbrales

            # Obtener todos los documentos de la coleccion
            documents = collection.find_one({"centro_id":centro_id }) #,"date": date

            umbrales_data = None
            if documents:
                umbrales_data = documents['datos'][-1][:4]
                
            return umbrales_data
    
    except Exception as e:
        print(f"Error al obtener datos de generador para centro {centro_id}': {e} {e.__traceback__.tb_lineno}")
        raise

def get_umbrales_scapp(db_scapp, centro_id):
    """
    Obtiene el ultimo set de umbrales para un centro desde la coleccion 'umbrales'
    en la base de datos scapp. Cada documento tiene la forma:
      {
        "centro_id": "...",
        "datos": [
          {
            "SFR":     {"A": int,   "C": int},
            "AYUNO":   {"A": float, "C": float},
            "ALARMAS": {"A": int,   "C": int},
            "fecha":   "YYYY-MM-DD HH:MM:SS"
          },
          ...
        ]
      }
    Devuelve JSON con el ultimo entry (incluye SFR, AYUNO, ALARMAS y fecha).
    En caso de error o ausente, devuelve jsonify con el codigo apropiado.
    """
    try:
        collection = db_scapp.umbrales
        print(f"{centro_id=}")
        doc = collection.find_one({'centro_id': centro_id})
        print(f"{doc=}")
        if not doc:
            return jsonify({
                'error': f'No se encontraron umbrales para centro_id "{centro_id}"'
            }), 404

        datos = doc.get('datos')
        if not isinstance(datos, list) or len(datos) == 0:
            return jsonify({
                'error': f'Documento de umbrales mal formado o sin datos para centro_id "{centro_id}"'
            }), 404

        ultimo = datos[-1]
        print(f"{ultimo=}")
        # ultimo entry ya tiene: SFR, AYUNO, ALARMAS, fecha
        return ultimo

    except Exception as e:
        # Logging interno para debug
        print(f"[get_umbrales_scapp] Error al obtener umbrales para centro_id={centro_id}: {e}")
        return jsonify({
            'error': 'Error interno al obtener umbrales'
        }), 500

#Funciones Auxiliares Umbrales SCAPP
def guardar_umbrales_scapp(client, username, umbrales):
    """
    Guarda o actualiza en la coleccion 'umbrales' un documento con:
      - centro_id
      - datos: lista de entradas donde cada entrada tiene directamente las llaves
        "SFR", "AYUNO", "ALARMAS" y "fecha"
    Ejemplo de entrada JSON esperada (payload):
    {
      "centro": "centro_001",
      "datos": {
        "SFR":     {"A": 50, "C": 80},
        "AYUNO":   {"A": 0.5, "C": 2.0},
        "ALARMAS": {"A": 10, "C": 30}
      }
    }
    """
    print("ENTRANDOA GUARDAR UMBRALES SCAPP!!!")
    # 1) Extraer parametros
    centro = umbrales.get('centro')
    nuevos = umbrales.get('data')
    if centro is None or nuevos is None:
        return jsonify({'error': "Se requieren los parametros 'centro' y 'datos'"}), 404

    # 2) Validacion de llaves esperadas
    expected_keys = {"SFR", "AYUNO", "ALARMAS"}
    if not isinstance(nuevos, dict) or set(nuevos) != expected_keys:
        return jsonify({'error': f"'datos' debe ser un dict con llaves {expected_keys}"}), 404

    # 3) Validar SFR y ALARMAS como enteros
    for key in ("SFR", "ALARMAS"):
        sub = nuevos.get(key)
        if not isinstance(sub, dict) or not {"A", "C"}.issubset(sub):
            return jsonify({'error': f"'{key}' debe ser un dict con llaves 'A' y 'C'"}), 404
        if not (isinstance(sub["A"], int) and isinstance(sub["C"], int)):
            return jsonify({'error': f"Los valores de '{key}' deben ser enteros"}), 404

    # 4) Validar AYUNO como floats
    ayuno = nuevos.get("AYUNO")
    if not isinstance(ayuno, dict) or not {"A", "C"}.issubset(ayuno):
        return jsonify({'error': "'AYUNO' debe ser un dict con llaves 'A' y 'C'"}), 404
    try:
        nuevos["AYUNO"]["A"] = float(ayuno["A"])
        nuevos["AYUNO"]["C"] = float(ayuno["C"])
    except (TypeError, ValueError):
        return jsonify({'error': "Los valores de 'AYUNO' deben ser numericos (float)"}), 404

    # 5) Crear la entrada con fecha y los umbrales al mismo nivel
    fecha = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    entrada = {
        **nuevos,
        "fecha": fecha
    }

    # 6) Conectar a la coleccion
    col = client[scapp_database_name].umbrales
    user_db = client[user_database_name]
    users_historial_collection = user_db.historial_users
    print("Conectados a la DB")
    try:
        # 7) Buscar documento existente
        existing = col.find_one({"centro_id": centro})
        
        # print(f"[guardar_umbrales_scapp] centro={centro}, nuevos={nuevos}")

        if existing:
            print("Entramos a existing umbrales")
            # ultimo registro
            last = existing.get("datos", [])[-1] if existing.get("datos") else {}
            # extraer solo los thresholds para comparar
            last_thresh = {k: v for k, v in last.items() if k in expected_keys}
            if last_thresh != nuevos:
                op = UpdateOne(
                    {"_id": existing["_id"]},
                    {"$push": {"datos": entrada}}
                )
                res = col.bulk_write([op])
                print(f"Actualizado: modified={res.modified_count}, upserted={res.upserted_count}")
            else:
                print(f"{centro}: sin cambios en umbrales, no se actualiza.")
        else:
            print("Entramos a nuevos umbrales")
            # insercion inicial
            col.insert_one({
                "centro_id": centro,
                "datos":     [entrada]
            })
            print(f"Nuevo documento de umbrales Scapp insertado en centro {centro}.")
            insert_log_in_user_history(users_historial_collection, username, centro, 'add_scapp_threshold')
    except Exception as e:
        print(f"Error guardando umbrales Scapp por error en linea {e.__traceback__.tb_lineno}: \n{e}")
        return jsonify({'error': "El Serrvidor encontro un error inesperado'"}), 500

    return


def enviar_umbrales_formateados_a_fe(client, service, centro_id):
    if service == "ENVIRO":
        # coleccion = col_enviro_umbrales
        coleccion = client[enviro_database_name].umbrales

    elif service == "NANOOX":
        # coleccion = col_nanoox_umbrales
        coleccion = client[nanoox_database_name].umbrales
    um= list(coleccion.find({"centro_id": centro_id},{"_id":0}))
    umbrales = []
    
    for documento in um:
        nombre = documento['variable']
        status = documento['status']
        tipo = documento['tipo']
        
        if tipo == 'simple':
            atencion = documento['c']
            critica = documento['d']
            umbrales.append({
                'nombre': nombre,
                'status': status,
                'tipo': tipo,
                'atencion': atencion if atencion is not None else None,
                'critica': critica if critica is not None else None,
            })
        elif tipo == 'compuesta':
            atencionInf = documento['a']
            criticaInf = documento['b']
            atencionSup = documento['c']
            criticaSup = documento['d']
            umbrales.append({
                'nombre': nombre,
                'status': status,
                'tipo': tipo,
                'atencionInf': atencionInf if atencionInf is not None else None,
                'criticaInf': criticaInf if criticaInf is not None else None,
                'atencionSup': atencionSup if atencionSup is not None else None,
                'criticaSup': criticaSup if criticaSup is not None else None
            })
    
    return(umbrales)

def actualizar_estado_servicio(client, centro_id, mensaje):
    # Selecciona la coleccion
    bulk_operations = []
    estado_centros_collection = client[user_database_name].alertas_fallas
    operation = UpdateOne(
        {
            'centro_id' : centro_id
        },
            {'$set':mensaje},
        
        upsert = True
    )

    bulk_operations.append(operation)
    if bulk_operations:
        estado_centros_collection.bulk_write(bulk_operations)
        print(f'Estado de {centro_id} actualizado en ITGSM')
        return True
    else:
        return False
    
def actualizar_estado_variables(client, centro_id, mensaje):
    col_enviro_variables = client[enviro_database_name].variables
    # Selecciona la coleccion
    bulk_operations = []
    operation = UpdateOne(
        {
            'centro_id' : centro_id
        },
            {'$set':mensaje},
        
        upsert = True
    )

    bulk_operations.append(operation)
    if bulk_operations:
        col_enviro_variables.bulk_write(bulk_operations)
        print(f'Estado variables de {centro_id} actualizado en ITGSM')
        return True
    else:
        return False
    

#---------------RUTAS------------------------!
#Para ITGSM Recibir alertas desde Local
# @app.route( '/<SERVICE>/post_alertas', methods=['POST'])
# def post_alertas(SERVICE):
#     if SERVICE == 'ENVIRO':
#         print(f'ENTRAMOS A RECEPCION DE ALERTAS')
#         alertas = request.json
#         if alertas != []:
#             pprint(alertas)
#             guardar_alertas_activas(alertas)
#             return jsonify({"mensaje": f"Se han enviado correctamente las alertas "}), 200
#         else:
#             print(f'No se han recibido datos')
#             return jsonify({"mensaje": f" No se han enviado correctamente las alertas"}), 401
#     elif SERVICE == 'NANOOX':
#         return jsonify({"mensaje": f" El servicio {SERVICE} no posee el metodo pero iguals"}), 200

#Post alertas con nanoox
@app.route( '/<SERVICE>/post_alertas', methods=['POST'])
def post_alertas(SERVICE):
    service = SERVICE
    alertas = request.json
    client = get_db()

    try:
        if alertas != []:
            # pprint(alertas)
            guardar_alertas_activas(client, service, alertas)
            return jsonify({"mensaje": f"Se han enviado correctamente las alertas {SERVICE} "}), 200
        else:
            print(f'No se han recibido datos de alertas')
            return jsonify({"mensaje": f"No se han recibido datos de alertas"}), 401
    except Exception as e:
        print(f"Error Enviando las alertas {SERVICE}: por error en linea {e.__traceback__.tb_lineno}: \n{e}")
        return jsonify({"mensaje": f" No se han enviado correctamente las alertas a ITGSM "}), 401

@app.route( '/<SERVICE>/alertas_activas/<CENTRO>/<AREA>/<REGION>', methods=['GET'])
def alertas_to_fe(SERVICE, CENTRO, AREA, REGION):
    client = get_db()
    CENTRO = CENTRO.capitalize()
    AREA = AREA.capitalize()
    REGION = REGION.capitalize()    
    # centro_id = f'{CENTRO}-{AREA}-{REGION}'
    centro_id = f'{REGION}-{AREA}-{CENTRO}'
    print(f'SERVICE_ALERTA_ACTIVA: {SERVICE}')
    # if SERVICE == 'ENVIRO':
    # alertas_activas = obtener_alertas_activas(centro_id)
    try:
        alertas_activas = obtener_alertas_activas(client, SERVICE, centro_id)
        print( f"Se han enviado correctamente las alertas al FE {SERVICE}")
        return jsonify(alertas_activas), 200
    except Exception as e:
        print(f"ERROR Enviando las alertas {SERVICE} por error en linea {e.__traceback__.tb_lineno}: \n{e}")
        return jsonify({"mensaje": f" No se han enviado correctamente las alertas al FE "}), 401 

#revision con nanoox
@app.route( '/<SERVICE>/revision_alerta', methods=['POST'])
def revisar_alerta_ITGSM(SERVICE):
    client = get_db()

    datos_alerta_revisada = request.json
    print(f'datos_alerta_revisada: {datos_alerta_revisada} \n')
    if datos_alerta_revisada:
        data_alertas_revisadas = datos_alerta_revisada['data']
        centro_id = datos_alerta_revisada['centro']
        
        if data_alertas_revisadas:
            # ids_revisados = []  # Lista para almacenar los _id de fallas que no pudieron revisarse
            ids_dict = {'no_revisados':[], 'revisados':[]}
            
            for falla in data_alertas_revisadas:
                idx = falla['_id']
                fecha_fin = falla['fin_estado']
                
                try:
                    actualizar_revision_alerta(client, SERVICE, centro_id, idx, 'local', fecha_fin)
                    actualizar_consolidado_alertas_revisadas(client, SERVICE, idx)
                    ids_dict["revisados"].append(idx)
                except Exception:
                    print("No se ha podido revisar la alerta con _id {idx}")
                    ids_dict["no_revisados"].append(idx)
                    # errores_ids.append(idx)  # Guardamos solo el _id en caso de error
            
            if ids_dict:
                print(f"alertas {ids_dict=}")
                return jsonify({
                    "mensaje": "Las alertas revisadas en SM son",
                    "data": ids_dict  # Devuelve un diccionario con los ids que se revisaron y los que no
                }), 200
            else:
                return jsonify({"mensaje": "No se han podido revisar las alertas enviadas"}), 401
        else:
            return jsonify({"mensaje": "Se enviaron revisiones de alertas vacias"}), 204
    else:
        return jsonify({"mensaje": "No se han enviado datos"}), 204
# def revisar_alerta_ITGSM(SERVICE): #,CENTRO_ID
#     print(f'datos_desde_RICHI: {request.json} \n')
#     datos_alerta_revisada = request.json
#     print(f'datos_alerta_revisada: {datos_alerta_revisada} \n')
#     if datos_alerta_revisada:
#         data_alertas_revisadas=datos_alerta_revisada['data']
#         if data_alertas_revisadas != []:
#             centro_id = datos_alerta_revisada['centro']
#             print(f"{centro_id=}")
#             for falla in datos_alerta_revisada['data']:
#                 idx = falla['_id']
#                 fecha_fin = falla['fin_estado']
#                 try:
#                     actualizar_revision_alerta(SERVICE, centro_id, idx, 'local', fecha_fin) #idx,
#                     actualizar_consolidado_alertas_revisadas(SERVICE, idx)
#                     # actualizar_consolidado_fallas_revisadas(SERVICE, centro_id, idx)
#                     return jsonify({"mensaje": f"Se han enviado correctamente las alertas "}), 200
#                 except Exception as e:
#                     print(f"No se han enviado correctamente las fallas por error en linea {e.__traceback__.tb_lineno}: \n{e}")
#                     return jsonify({"mensaje": f" No se han enviado correctamente las alertas "}), 401
#         else:
#             return jsonify({"mensaje": f"Se enviaron revisiones de alertas vacias"}), 204
#     else:
#         return jsonify({"mensaje": f"No se han enviado datos"}), 204

@app.route( '/<SERVICE>/fin_estado_alerta_activa/<CENTRO_ID>', methods=['POST'])
def actualizar_fin_estado_alerta_ITGSM(SERVICE, CENTRO_ID):
    print(f'datos_desde_LOCAL: {request.json} \n')
    datos_alerta_no_revisada = request.json
    client = get_db()
    col_enviro_alertas = client[enviro_database_name].alertas
    print(f'{datos_alerta_no_revisada=} \n')
    if datos_alerta_no_revisada:
        idx = datos_alerta_no_revisada['idx']
        fecha_fin = datos_alerta_no_revisada['fin_estado']
        try:
            actualizar_fin_estado_alerta(client, SERVICE, CENTRO_ID, idx, fecha_fin)
            if check_alerta_incompleta(col_enviro_alertas, idx):
                return jsonify({"mensaje": f"Se ha enviado correctamente el fin estado alerta pero no cunple para actualizar consolidado "}), 200
            else:
                actualizar_consolidado_alertas_revisadas(client,SERVICE, idx)
                return jsonify({"mensaje": f"Se han enviado correctamente el fin estado alerta e ingresado al consolidado"}), 200 
        
        except Exception as e:
            print(f"Error enviando las alertas {SERVICE} por error en linea {e.__traceback__.tb_lineno}: \n{e}")
            return jsonify({"mensaje": f" No se ha enviado correctamente el fin estado alerta "}), 401

# @app.route( '/<SERVICE>/revision_alerta/<CENTRO_ID>', methods=['POST'])
# def revisar_alerta_ITGSM(SERVICE, CENTRO_ID):
#     print(f'datos_desde_SALVA: {request.json} \n')
#     if SERVICE == 'ENVIRO':
#         datos_alerta_revisada = request.json
#         print(f'datos_alerta_revisada: {datos_alerta_revisada} \n')
#         if datos_alerta_revisada:
#             idx = datos_alerta_revisada['idx']
#             usuario_revision = datos_alerta_revisada['usuario_revision']
#             fecha_fin = datos_alerta_revisada['fin']
#             try:
#                 actualizar_revision_alerta(CENTRO_ID, idx, usuario_revision, fecha_fin)
#                 actualizar_consolidado_alertas_revisadas(idx)
#                 return jsonify({"mensaje": f"Se han enviado correctamente las alertas "}), 200
#             except Exception as e:
#                 print(f"No se han enviado correctamente las alertas por error en linea {e.__traceback__.tb_lineno}: \n{e}")
#                 return jsonify({"mensaje": f" No se han enviado correctamente las alertas "}), 401
#     elif SERVICE == 'NANOOX':
#         return jsonify({"mensaje": f"Se han enviado correctamente las alertas "}), 200

@app.route( '/<SERVICE>/post_fallas', methods=['POST'])
def post_fallas(SERVICE):
    client = get_db()
    service = SERVICE
    fallas = request.json
    try:
        if fallas != []:
            # pprint(fallas)
            guardar_fallas_activas(client, service, fallas)
            return jsonify({"mensaje": f"Se han recibido correctamente las fallas del servicio {service}"}), 200
        else:
            print(f'No se han recibido datos de fallas del servicio')
            return jsonify({"mensaje": f" No se han recibido datos de fallas del servicio {service}"}), 401
    except Exception as e:
        print(f"No se han enviado correctamente las fallas por error en linea {e.__traceback__.tb_lineno}: \n{e}")
        return jsonify({"mensaje": f" No se han enviado correctamente las fallas a ITGSM "}), 401
    
@app.route( '/<SERVICE>/fallas_activas/<CENTRO>/<AREA>/<REGION>', methods=['GET'])
def fallas_to_fe(SERVICE, CENTRO, AREA, REGION):
    """ try:
        print(f"faia{REGION[0]=}")
        print(f"faia{REGION[1]=}")
    except:
        print("NO SE PUEDE SEPRAR REGION") """
    client = get_db()
    CENTRO = CENTRO.capitalize()
    AREA = AREA.capitalize()
    REGION = REGION.capitalize()
    # centro_id = f'{CENTRO}-{AREA}-{REGION}'
    centro_id = f'{REGION}-{AREA}-{CENTRO}'
    print(f'centro_id: {centro_id}')
    try:
        fallas_activas = obtener_fallas_activas(client, SERVICE, centro_id)
        print( f"Se han enviado correctamente las fallas al FE")
        return jsonify(fallas_activas), 200
    except Exception as e:
        print(f"No se han enviado correctamente las fallas por error en linea {e.__traceback__.tb_lineno}: \n{e}")
        return jsonify({"mensaje": f" No se han enviado correctamente las fallas al FE "}), 401
    
@app.route( '/<SERVICE>/revision_falla', methods=['POST'])
def revisar_falla_ITGSM(SERVICE): #,CENTRO_ID
    # print(f'datos_desde_RICHI: {request.json} \n')
    client = get_db()
    print("REVISANDO FALLAS****-*-*-*-")
    fallas_revisada = request.json
    print(f'datos_falla_revisada: {fallas_revisada} \n')
    if fallas_revisada:
        data_fallas_revisadas = fallas_revisada['data']
        centro_id = fallas_revisada['centro']

        if data_fallas_revisadas:
            # centro_id = fallas_revisada['centro']
            ids_dict = {'no_revisados':[], 'revisados':[]}
            # ids_revisados =[]
            # ids_no_revisados =[]

            for falla in data_fallas_revisadas:
                idx = falla['_id']
                fecha_fin = falla['fin_estado']
                print("Variables en falla:")
                print(SERVICE, centro_id, idx, 'local', fecha_fin)
                try:
                    actualizar_revision_falla(client, SERVICE, centro_id, idx, 'local', fecha_fin)
                    actualizar_consolidado_fallas_revisadas(client, SERVICE,  idx)
                    ids_dict["revisados"].append(idx)
                except Exception:
                    print("No se ha podido revisar la alerta con _id {idx}")
                    ids_dict["no_revisados"].append(idx)

            if ids_dict:
                print(f"fallas {ids_dict=}")
                return jsonify({
                    "mensaje": "Las alertas revisadas en SM son",
                    "data": ids_dict  # Devuelve un diccionario con los ids que se revisaron y los que no
                }), 200
            else:
                return jsonify({"mensaje": "No se han podido revisar las alertas enviadas"}), 401
        else:
            return jsonify({"mensaje": "Se enviaron revisiones de alertas vacias"}), 204
    else:
        return jsonify({"mensaje": "No se han enviado datos"}), 204

    #             try:
    #                 actualizar_revision_falla(SERVICE, centro_id, idx, 'local', fecha_fin)
    #                 # actualizar_consolidado_fallas_revisadas(SERVICE, idx) #idx
    #                 actualizar_consolidado_fallas_revisadas(SERVICE,  idx) #centro_id,
    #                 return jsonify({"mensaje": f"Se han enviado correctamente las fallas "}), 200
    #             except Exception as e:
    #                 print(f"No se han enviado correctamente las fallas por error en linea {e.__traceback__.tb_lineno}: \n{e}")
    #                 return jsonify({"mensaje": f" No se han recibido correctamente las fallas "}), 401
    #     else:
    #         return jsonify({"mensaje": f"Se enviaron revisiones de fallas vacias"}), 204
    # else:
    #     return jsonify({"mensaje": f"No se han enviado datos"}), 204





def guardar_generadores_metadata(client,  payload, collection_name="metadata_generadores"):
    """
    Guarda la metadata de generadores en Mongo.
    Cada documento mantiene: generator_name, quadrants, centro_id.
    
    payload ejemplo:
    {
      "centro": "Fonchi-Richi-Salva",
      "data": {
        "generator_1": {"quadrants": ["quadrant_1", "quadrant_2"]},
        "generator_2": {"quadrants": ["quadrant_3"]}
      }
    }
    """

    centro_id = payload.get("centro")
    generators = payload.get("data", {})

    if not centro_id or not generators:
        print(" Datos inválidos, no se guardó nada.")
        return
    nanoox_db = client[nanoox_database_name]
    center_name = centro_id.split("-")[-1]
    col = nanoox_db[f"{center_name}_{collection_name}"]
    operaciones = []

    for gen_name, gen_info in generators.items():
        quadrants = gen_info.get("quadrants", [])

        if gen_name == "0":
            print("bug conocido: Nombre de generador invalido ")
            continue
        
        # UpdateOne con upsert para actualizar si existe o crear nuevo
        operaciones.append(
            UpdateOne(
                {"generator_name": gen_name, "centro_id": centro_id},
                {"$set": {
                    "generator_name": gen_name,
                    "quadrants": quadrants,
                    "centro_id": centro_id
                }},
                upsert=True
            )
        )

    if operaciones:
        result = col.bulk_write(operaciones)
        print(f"✅ Generadores guardados: {result.modified_count} modificados, {result.upserted_count} insertados.")



@app.route( '/<SERVICE>/post_generadores', methods=['POST'])
def post_generadores_desde_local(SERVICE):
    print("=============== ENTRANDO A RECIBIR GENERADORES ===============")

    generators_received_data = request.json
    print(f'{generators_received_data=}')

    centro_id = generators_received_data.get("centro")
    generators_data = generators_received_data.get("data")

    print(f"{generators_data=}")

    client = get_db()
    
    guardar_generadores_metadata(client, payload= generators_received_data)

    return "OK", 200
    

@app.route( '/<SERVICE>/post_umbrales', methods=['POST'])
def post_umbrales_desde_local(SERVICE):
    # print("=============== ENTRANDO A RECIBIR UMBRALES ===============")
    service = SERVICE.upper()
    umbrales = request.json
    client = get_db()

    flag_new_version = "control_version" in umbrales

    if flag_new_version:
        print("ES VERSION ITG ANEC")


    if service =="ENVIRO":

        umbrales = request.json
        if umbrales != []:
            pprint(umbrales)
            guardar_umbrales_activos(client, service, umbrales)
            return jsonify({"mensaje": f"Se han enviado correctamente los umbrales "}), 200
        else:
            print(f'No se han recibido datos de umbrales')
            return jsonify({"mensaje": f" No se han enviado correctamente los umbrales a ITGSM"}), 401
        

        
    elif service =="NANOOX":
        print(f"Post_umbrales Nanoox {umbrales=}\n")
        if umbrales:
            guardar_umbrales_nanoox(client, umbrales, new_version = flag_new_version)
            return jsonify({"mensaje": f"Se han enviado correctamente los umbrales "}), 200
        else:
            print(f'No se han recibido datos de umbrales')
            return jsonify({"mensaje": f" No se han enviado correctamente los umbrales a ITGSM"}), 401
    # elif service =="SCAPP":
    #     print(f"Post_umbrales Scapp {umbrales=}\n")
    #     if umbrales:
    #         guardar_umbrales_scapp(client, umbrales)
    #         return jsonify({"mensaje": f"Se han enviado correctamente los umbrales "}), 200
    #     else:
    #         print(f'No se han recibido datos de umbrales Scapp')
    #         return jsonify({"mensaje": f" No se han enviado correctamente los umbrales a ITGSM"}), 401       

@app.route( '/<SERVICE>/def_umbrales_region', methods=['POST'])
@jwt_required()
def post_umbrales_desde_scapp_local(SERVICE):
    username = get_jwt_identity()
    service = SERVICE.upper()
    umbrales = request.json
    

    if service =="SCAPP":
        print(f"Post_umbrales Scapp {umbrales=}\n")
        if umbrales:
            client = get_db()
            guardar_umbrales_scapp(client, username, umbrales)
            return jsonify({"mensaje": f"Se han enviado correctamente los umbrales "}), 200
        else:
            print(f'No se han recibido datos de umbrales Scapp')
            return jsonify({"mensaje": f" No se han enviado correctamente los umbrales a ITGSM"}), 401     


@app.route('/<SERVICE>/obtener_umbrales_actuales/<CENTRO>/<AREA>/<REGION>', methods=['GET'])
def obtener_umbrales_fe(SERVICE, CENTRO, AREA, REGION):
    CENTRO = CENTRO.capitalize()
    AREA = AREA.capitalize()
    REGION = REGION.capitalize()
    centro_id = f'{CENTRO}-{AREA}-{REGION}'
    client = get_db()
    nanoox_db = client[nanoox_database_name]
    scapp_db = client[scapp_database_name]

    # print(f'{centro_id=}')
    if SERVICE == "ENVIRO":
        umbrales = enviar_umbrales_formateados_a_fe(client, SERVICE, centro_id)
        return jsonify(umbrales), 200
    elif SERVICE == "NANOOX":
        centro_id = convertir_mal_menor(centro_id)
        umbrales = get_umbrales_nanoox(nanoox_db, centro_id)
        return jsonify(umbrales), 200
    elif SERVICE == "SCAPP":
        centro_id = convertir_mal_menor(centro_id)
        umbrales = get_umbrales_scapp(scapp_db, centro_id)
        return jsonify(umbrales), 200
#Post notas transparente a servicios
@app.route( '/<SERVICE>/post_notas/<CENTRO_ID>', methods=['POST'])
def post_notas(SERVICE, CENTRO_ID):
    client = get_db()
    service = SERVICE
    notas = request.json
    centro_id = convertir_mal_menor(CENTRO_ID)
    try:
        if notas != []:
            guardar_notas(client, service,centro_id, notas)
            return jsonify({"mensaje": f"Se han recibido datos de las notas del servicio {service}"}), 200
        else:
            print(f'No se han recibido datos de notas')
            return jsonify({"mensaje": f" No se han recibido datos de notas del servicio {service}"}), 401
    except Exception as e:
        print(f"No se han enviado correctamente las notas por error en linea {e.__traceback__.tb_lineno}: \n{e}")
        return jsonify({"mensaje": f" No se han enviado correctamente las notas a ITGSM "}), 401

#------------ESTADOS SERVICIOS--------------------#
@app.route('/<SERVICE>/post_estado_servicio_por_centro/<CENTRO>/<AREA>/<REGION>', methods=['POST'])
# @app.route('/post_estado_servicio_por_centro/<CENTRO>/<AREA>/<REGION>', methods=['POST'])
def recepcion_estado_centro(SERVICE, CENTRO,AREA,REGION):
    estado_centro = request.json
    CENTRO = CENTRO.capitalize()
    AREA = AREA.capitalize()
    REGION = REGION.capitalize()
    centro_id = f'{CENTRO}-{AREA}-{REGION}'
    print(f'centro_id_post_estado_desde_local: {centro_id}')
    client = get_db()

    try:
        actualizar_estado_servicio(client, centro_id.lower(), estado_centro)
        print(f'Se ha recibido correctamente el estado de servicio del centro {centro_id} para el servicio {SERVICE}')
        return jsonify({"mensaje": f"Se ha recibido correctamente el estado de servicio del centro {centro_id} para el servicio {SERVICE} "}), 200
    except Exception as e:
        print(f"No Se ha recibido correctamente el estado de servicio del centro {centro_id} para el servicio {SERVICE} por error en linea {e.__traceback__.tb_lineno}: \n{e}")
        return jsonify({"mensaje": f" No Se ha recibido correctamente el estado de servicio del centro {centro_id} para el servicio {SERVICE} "}), 401 

# @app.route('/estado_servicio/<AREA>/<REGION>', methods=['GET'])
# def estado_servicio( AREA, REGION):
#     AREA = AREA.capitalize()
#     REGION = REGION.capitalize()

#     try:
#         estados = agregar_estado_centros_a_consolidado(agrupar_por_region_area(obtener_servicios_por_region_y_area(AREA, REGION)[0]), estados_servicios_region_area(REGION, AREA))
#         return jsonify(estados), 200
#     except Exception as e:
#         return jsonify({"mensaje": f"Error solicitando estados en linea {e.__traceback__.tb_lineno}: \n{e}"}), 401

@app.route('/estado_servicio_2/<AREA>/<REGION>', methods=['GET'])
# @app.route('/estado_servicio/<AREA>/<REGION>', methods=['GET'])
@jwt_required()
def estado_servicio( AREA, REGION):
    """
    Permisos de usuarios y estado dentro de Servicios
    """
    try:
        username = get_jwt_identity()
    except Exception as e:
        print("error autenticando", e)
    # username = USER
    AREA = AREA.capitalize()
    REGION = REGION.capitalize()
    client = get_db()
    try:
        # print(f'{obtener_servicios_por_region_y_area(client, username)=}')
        # print(f'{estados_servicios_region_area_2(client, username)=}')
        estados = agregar_estado_centros_a_consolidado(agrupar_por_region_area(obtener_servicios_por_region_y_area(client, username)), estados_servicios_region_area_2(client, username))
        # print(f'estado_servicios: {estados}')
        permisos_usuario = procesar_permisos_de_usuario(client, username)
        # print(f'permisos_usuario: {permisos_usuario}')
        respuesta = update_status_service(estados, permisos_usuario)
        # print(f'estado_servicio_2: {respuesta}')
        return jsonify(respuesta), 200
    except Exception as e:
        print("En la excepcion", e, e.__traceback__.tb_lineno)
        return jsonify({"mensaje": f"Error solicitando estados en linea {e.__traceback__.tb_lineno}: \n{e}"}), 401


@app.route('/<SERVICE>/estado_centro/<CENTRO>/<AREA>/<REGION>', methods=['GET'])
def get_estado_centro(SERVICE, CENTRO, AREA, REGION):
    CENTRO = CENTRO.capitalize()
    AREA = AREA.capitalize()
    REGION = REGION.capitalize()
    centro_id = f'{CENTRO}-{AREA}-{REGION}'
    print(f'centro_id: {centro_id}')
    client = get_db()
    if SERVICE == 'ENVIRO':
        try:
            estado_servicio_enviro = estado_enviro(client, centro_id)
            estado_servicio_nanoox = None
            print(f'estado_enviro:{estado_servicio_enviro}')
            print(f'estado_centro: {estado_centro(estado_servicio_enviro, estado_servicio_nanoox)}')
            estado = estado_centro(estado_servicio_enviro, estado_servicio_nanoox)
            return jsonify(estado), 200
        except Exception as e:
            print(f"Error solicitando estado centro {centro_id} en linea {e.__traceback__.tb_lineno}: \n{e}")
            return jsonify({"mensaje": f"Error solicitando estado centro {centro_id}"}), 401



# @app.route('/consolidado_estados', methods=['GET'])
#?ORIGINAL
# @app.route('/<PAGE>/consolidado_estados', methods=['GET'])
# @jwt_required()
# # def consolidado_estados_por_empresa():
# def consolidado_estados_por_empresa(PAGE):
#     """
#     Envia informacion de los estados de los centros (B,A,C) al \Home y dentro de cada Servicio
#     """
    
#     try:
#         client = get_db()
#         print(f"---------PAGE RECIBIDA-------: {PAGE} \n")
#         username = get_jwt_identity()
#         # print(f'{username=}')
#         # consolidado = consolidado_empresa_por_usuario_activo(username)
#         # consolidado = consolidado_empresa_por_usuario_activo_y_servicio(username, PAGE)
#         consolidado = consolidado_empresa_por_usuario_activo_y_servicio_2(client, username, PAGE)
#         print(f'consolidado_estados: {consolidado}')
#         return jsonify(consolidado), 200
#     except Exception as e:
#         print(f"Error obteniendo consolidado estados en linea {e.__traceback__.tb_lineno}: \n{e}")
#         return jsonify({"mensaje": f"Error obteniendo consolidado estados"}), 401

#PRUEBA NUEVO-----------------------------------
@app.route('/<PAGE>/consolidado_estados', methods=['GET'])
@jwt_required()
def consolidado_estados_por_empresa(PAGE: str):
    """
    Envia JSON con estados consolidados de centros, areas y regiones.
    """
    print(f"---------PAGE RECIBIDA-------: {PAGE} \n")
    try:
        client = get_db()
        username = get_jwt_identity()
        consolidado = consolidado_empresa(client, username, PAGE)
        # print(f'consolidado_estados new: {consolidado}')
        return jsonify(consolidado), 200
    except Exception as e:
        # app.logger.error(f"Error en consolidado_estados: {e}", exc_info=True)
        print(f"Error obteniendo consolidado estados en linea {e.__traceback__.tb_lineno}: \n{e}")
        return jsonify({"mensaje": "Error obteniendo consolidado estados"}), 500

#------------------------------------------

@app.route('/<SERVICE>/post_estado_variables/<CENTRO>/<AREA>/<REGION>', methods=['POST'])
# @app.route('/post_estado_servicio_por_centro/<CENTRO>/<AREA>/<REGION>', methods=['POST'])
def recepcion_estado_variables(SERVICE, CENTRO,AREA,REGION):
    json = request.json
    CENTRO = CENTRO.capitalize()
    AREA = AREA.capitalize()
    REGION = REGION.capitalize()
    centro_id = f'{CENTRO}-{AREA}-{REGION}'
    print(f'centro_id_post_estado_variables_desde_local: {centro_id}')
    client = get_db()
    try:
        actualizar_estado_variables(client, centro_id, json)
        print(f'Se ha recibido correctamente el estado de servicio del centro {centro_id} para el servicio {SERVICE}')
        return jsonify({"mensaje": f"Se ha recibido correctamente el estado de servicio del centro {centro_id} para el servicio {SERVICE} "}), 200
    except Exception as e:
        print(f"No Se ha recibido correctamente el estado de servicio del centro {centro_id} para el servicio {SERVICE} por error en linea {e.__traceback__.tb_lineno}: \n{e}")
        return jsonify({"mensaje": f" No Se ha recibido correctamente el estado de servicio del centro {centro_id} para el servicio {SERVICE} "}), 401 

@app.route('/<SERVICE>/estados_variables_actuales/<CENTRO>/<AREA>/<REGION>', methods=['GET'])
def get_var_state(SERVICE,CENTRO,AREA,REGION):
    CENTRO = CENTRO.capitalize()
    AREA = AREA.capitalize()
    REGION = REGION.capitalize()
    centro_id = f'{CENTRO}-{AREA}-{REGION}'
    client = get_db()
    col_enviro_variables = client[enviro_database_name].variables
    # Consulta con proyeccion para devolver solo los campos "atencion", "critica" y "emails"
    resultado = col_enviro_variables.find(
        {'centro_id': centro_id},
        {
            '_id': 0,  # Excluir el campo _id
            'atencion': 1,
            'critica': 1,
            'centro_id': 1,
            'fecha': 1
        }
    )
    # Convertir el cursor a una lista de documentos
    try:
        documentos = list(resultado)
        # print('{documentos=}')
        if documentos:
            documentos = documentos[0]
            print( f"Se han enviado correctamente las variables en mal estado hacia el FE")
            return jsonify(datos=documentos), 200
    except Exception as e:
        print(f"No se han enviado correctamente las variables en mal estado por error en linea {e.__traceback__.tb_lineno}: \n{e}")
        return jsonify({"mensaje": f" No se han enviado correctamente las variables en mal estado hacia el FE "}), 401

@app.route('/<SERVICE>/tipo_control/<CENTRO>/<AREA>/<REGION>', methods=['GET'] )
def get_tipo_control(SERVICE, CENTRO,AREA,REGION):
    CENTRO = CENTRO.capitalize()
    AREA = AREA.capitalize()
    REGION = REGION.capitalize()
    centro_id = f'{CENTRO}-{AREA}-{REGION}'
    try:
        client = get_db()
        nanoox_db = client[nanoox_database_name]
        tipo_control = get_control_by_centro_id(nanoox_db, centro_id)
        print(f"Se ha obtenido correctamente el tipo de control del centro {centro_id} ")
        return jsonify(datos=tipo_control), 200
    except Exception as e:
        print(f"No se ha obtenido correctamente el tipo de control del centro {centro_id} por error en linea {e.__traceback__.tb_lineno}: \n{e}")
        return jsonify({"mensaje": f" No se ha enviado correctamente el tipo de control del centro {centro_id} hacia el FE "}), 401

##RUTAS Y FUNCIONES PARA ANALiTICA

#Recepcion de datos desde remotos-Scapp
@app.route('/scapp/mirror_analytics/<REGION>/<AREA>/<CENTRO>', methods=['POST'] )
def scapp_analytics_saving(CENTRO,AREA,REGION):
    """
    Recepcion de datos o coleccion de pendientes desde locales Scapp
    Input Format:
    {{"timestamp": {
        "$date": "2025-05-13T11:24:00.000Z"
    },
    "camera_id": "4-A",  # ID de la camara o sensor origen de los datos

    "data": {
        "name": "4",  # Nombre o ID interno de la camara

        "activity_data": [  # Lista cronologica de mediciones de actividad
        {
            "time": "2025-04-28T07:45:00.999000",  # Timestamp (ISO 8601)
            "value": 54.85                         # Valor de actividad (float)
        },
        ...
        ],

        "alarms_by_tasa": {  # Conteo de alarmas por nivel de tasa
        "baja": 0,
        "media": 0,
        "alta": 0
        },

        "alarms_by_tasa_levels": {  # Umbrales utilizados para clasificar las tasas
        "baja": [0, 8],
        "media": [12],
        "alta": [22.11]
        },

        "alarms_data": [],  # Lista de alarmas detalladas (actualmente vacia)

        "feeding_time_range": {  # Intervalo detectado de alimentacion
        "inicio": "2025-04-28T11:26:42.882000",
        "final": "2025-04-28T14:07:17.477000"
        },

        "tasa_data": [  # Serie temporal de valores de tasa
        {
            "time": "2025-04-28T07:30:00.999000+00:00",
            "value": 0
        },
        ...
        ],

        "tasa_values": [  # Conjunto de valores unicos de tasa observados
        0, 8, 22.11, 12
        ],

        "values_summary": {  # Resumen estadistico del proceso
        "alarms_by_ton": 0,        # Conteo de alarmas por tonelada
        "alarms_count": 0,         # Total de alarmas detectadas
        "alarms_threshold": 3,     # Umbral minimo para considerar una alarma
        "delivered_food": 3123.34, # Cantidad total de comida entregada (kg)
        "fasts_count": 0,          # Numero de ayunos detectados
        "perdidos_count": 0,        # Conteo de eventos perdidos
        "sfr_value": 57,           # Valor SFR (indicador especifico del sistema)
        "tasa_units": "kg/min",    # Unidades usadas para tasa
        "visitas_count": 1          # Numero de visitas detectadas
        }
    },

    "_id": {
        "$oid": ""  # ID unico del documento en MongoDB
    }}
    """
    t1 = datetime.now()
    print(f"ENTRANDO A MIRROR ANALYTICS \n")
    
    CENTRO = CENTRO.capitalize()
    AREA = AREA.capitalize()
    REGION = REGION.capitalize()
    centro_id = f'{REGION}-{AREA}-{CENTRO}'
    received_json = request.json
    # print(f"{received_json=} \n")
    # raw_json = json.dumps(received_json)
    # proccess_json = json_util.loads(json.dumps(received_json))
    received_json = request.json
    # proccess_json = received_json
    proccess_json = json.loads(received_json, object_hook=json_util.object_hook)
    if proccess_json:
        print(f"JSON recibido desde {centro_id}")
    
    
    # print(f"{proccess_json=} \n")
    
    try:
        client = get_db()
        scapp_db = client[scapp_database_name]

        bulk_scapp_analytics(scapp_db, proccess_json, centro_id)
        
        print(f"Se ha guardado exitosamente la analitica Scapp del centro {centro_id}")
        t_end = datetime.now()
        print(f"Datos de Analitica guardados en {(t_end-t1).total_seconds()*1e3:.4f}ms.")
        return jsonify({"mensaje": "Datos de analitica Scapp recibidos exitosamente por servidor"}), 200
    except Exception as e:
        print(f"No se ha guardado correctamente a analitica Scapp del centro {centro_id} por error en linea {e.__traceback__.tb_lineno}: \n{e}")
        t_end = datetime.now()
        print(f"Datos de Analitica procesados en {(t_end-t1).total_seconds()*1e3:.4f}ms.")
        return jsonify({"mensaje": "Datos recibidos erroneamente por servidor"}), 401


def bulk_scapp_analytics(scapp_db, received_json, centro_id):
    """
    Acepta received_json como dict o lista de dicts.
    Para cada documento:
      * Calcula el rango de dia (ignora hora).
      * Borra el doc existente para ese centro_id + camera_id + fecha.
      * Inserta el nuevo.
    Agrupa todas las operaciones en dos bulk_writes: primero borrados, luego inserciones.
    """
    col_name = "data_analytic"
    # Crear coleccion time-series si no existe
    if col_name not in scapp_db.list_collection_names():
        scapp_db.create_collection(
            col_name,
            timeseries={
                "timeField": "timestamp",
                "metaField": "metadata",
                "granularity": "hours"
            }
        )
    coll = scapp_db[col_name]

    # Normalizamos a lista
    print(f"{type(received_json)=}")
    if isinstance(received_json, dict):
        docs = [received_json]
    elif isinstance(received_json, list):
        docs = received_json
    else:
        raise ValueError("received_json debe ser dict o lista de dicts")

    ops_delete = []
    ops_insert = []
    ops = []

    #Timestamp para hora de actualizacion en FE
    updated_ts = datetime.now()

    for doc_in in docs:
        print(f"{type(doc_in)=} ")
        print(f"{doc_in.keys()=}")        
        ts = doc_in.get("timestamp")
        cam = doc_in.get("camera_id")

        # Validacion de timestamp
        if not isinstance(ts, datetime):
            try:
                ts = datetime.fromisoformat(ts)
            except Exception as e:
                print(f"WARNING: timestamp invalido {ts=}:", e.__traceback__.tb_lineno, e)
                return jsonify({"mensaje": "Datos recibidos con sintaxis erronea"}), 400

        if cam is None:
            raise ValueError("Falta 'camera_id' en alguno de los documentos")

        # Determinar rango de dia
        tz = ts.tzinfo or timezone.utc
        start_day = datetime(ts.year, ts.month, ts.day, tzinfo=tz)
        next_day = start_day + timedelta(days=1)

        # Filtro para borrar
        filtro = {
            "centro_id": centro_id,
            "camera_id": cam,
            "timestamp": {"$gte": start_day, "$lt": next_day}
        }
    #     # borrado masivo en vez de uno a uno
        ops_delete.append(DeleteMany(filtro))
        # ops_delete.append(DeleteOne(filtro))

        # Preparar documento para insertar (sin _id)
        new_doc = {
            **{k: v for k, v in doc_in.items() if k != "_id"},
            "centro_id": centro_id,
            "timestamp": ts,
            "updated": updated_ts 
        }
        ops_insert.append(InsertOne(new_doc))

    # Ejecutar bulk de borrados
    try:
        if ops_delete:
            result_del = coll.bulk_write(ops_delete, ordered=False)
            print(f"centro: {centro_id}| fecha: {ts.year}-{ts.month}-{ts.day}  -> Borrados: {result_del.deleted_count}")
    except BulkWriteError as bwe:
        print("Error en bulk_write de borrados:", bwe.details)
    except Exception as e:
        print("Error inesperado en borrados:", e.__traceback__.tb_lineno, e)

    # Ejecutar bulk de inserciones
    try:
        if ops_insert:
            result_ins = coll.bulk_write(ops_insert, ordered=False)
            print(f"centro: {centro_id}| fecha: {ts.year}-{ts.month}-{ts.day} -> Insertados: {result_ins.inserted_count}")
    except BulkWriteError as bwe:
        print("Error en bulk_write de inserciones:", bwe.details)
    except Exception as e:
        print("Error inesperado en inserciones:", e.__traceback__.tb_lineno, e)

    return

# def bulk_scapp_analytics(scapp_db, received_json, centro_id):
#     """ 
#     ORIGINAL
#     Acepta received_json como dict o lista de dicts.  
#     Para cada documento:
#       * Calcula el rango de dia (ignora hora).  
#       * Borra el doc existente para ese centro_id + camera_id + fecha.  
#       * Inserta el nuevo.  
#     Agrupa todas las operaciones en un solo bulk_write.
#     """
#     #Validacion de existencia de coleccion timeseries en DB
#     col_name = "data_analytic"
#     # Crear coleccion time-series si no existe
#     if col_name not in scapp_db.list_collection_names():
#         scapp_db.create_collection(
#             col_name,
#             timeseries={
#                 "timeField": "timestamp",
#                 "metaField": "metadata",
#                 "granularity": "hours"
#             }
#         )

#     coll = scapp_db[col_name]
#     # Normalizamos a lista
#     docs = []
#     print(f"{type(received_json)=}")
#     if isinstance(received_json, dict):
#         docs = [received_json]
#     elif isinstance(received_json, list):
#         docs = received_json
#     else:
#         raise ValueError("received_json debe ser dict o lista de dicts")

#     ops = []
#     global_ts = None
#     for doc_in in docs:
#         print(f"{type(doc_in)=} ")
#         print(f"{doc_in.keys()=} /n ")
#         ts  = doc_in.get("timestamp")
#         cam = doc_in.get("camera_id")
#         if not isinstance(ts, datetime):
#             try:
#                 ts = datetime.fromisoformat(ts)
#             except Exception as e:
#                 print(f"WARNIING de timestamp invalido en documento {ts=}:", e.__traceback__.tb_lineno, e)
#                 return jsonify({"mensaje": "Datos recibidos con sintaxis erronea"}), 400

#                 # if not global_ts:
#                 #     raise ValueError(f"'timestamp' invalido en documento {ts=}") from e
                
#                 # print(f"Warning: 'timestamp' invalido en documento - Se utiliza timestamp de otros documentos")          
#                 # ts = copy.deepcopy(ts)  
#         # if not isinstance(ts, datetime):
#         #     print(f"Convirtiendo {ts} a datetime")
#         #     ts = datetime.fromisoformat(ts)
#         #     if not isinstance(ts, datetime):
#         #         raise ValueError("El campo 'timestamp' debe ser un datetime en cada documento")
#         if cam is None:
#             raise ValueError("Falta 'camera_id' en alguno de los documentos")

#         print(f"{ts=}")
#         # rango de dia completo (00:00 ≤ ts < siguiente 00:00)
#         tz        = ts.tzinfo or timezone.utc
#         start_day = datetime(ts.year, ts.month, ts.day, tzinfo=tz)
#         next_day  = start_day + timedelta(days=1)

        
#         # filtro por centro + camara + dia
#         filtro = {
#             "centro_id": centro_id,
#             "camera_id": cam,
#             "timestamp": {"$gte": start_day, "$lt": next_day}
#         }

#         # documento completo a insertar (reemplaza todo salvo _id)
#         doc = {"centro_id": centro_id}
#         doc.update({k: v for k, v in doc_in.items() if k != "_id"})
#         doc["timestamp"] = ts

#         # agregamos Delete + Insert
#         ops.append(DeleteOne(filtro)) #Elimina los datos de un centro y camara id para guardar los mas actualizados del dia
#         ops.append(InsertOne(doc)) # Inserta nuevos datos actualizados de un centro y camara id

#     # ejecutamos todas las operaciones en un solo bulk
#     try:
#         result = coll.bulk_write(ops, ordered=True)
#         print(f"centro: {centro_id}| fecha: {ts.year}-{ts.month}-{ts.day} -> Borrados: {result.deleted_count}, Insertados: {result.inserted_count}")
#     except BulkWriteError as bwe:
#         print("Error en bulk_write:", bwe.details)
#     except Exception as e:
#         print("Error inesperado:", e.__traceback__.tb_lineno, e)


# Ultima hora de dato recibido desde Centro SCAPP
@app.route('/scapp/last_received_data', methods=['GET'])
def last_received_data():
    print("Entrando a SCAPP-LRD")
    centro_id= None
    try:
        # Leer centro_id desde la query string
        centro_id = request.args.get('centro_id')
        centro_id=capitalizar_centro_id(centro_id)
        print(f"{request.args=}")
        # print(f"LRD out: {centro_id=}")
    except Exception as e:
        print( f"{e.__traceback__.tb_lineno}: \n{e}")

    if not centro_id:
        print('error:' 'Falta el parametro centro_id')
        return jsonify({'error': 'Falta el parametro centro_id'}), 400

    # calculamos inicio de hoy
    start_today = get_start_of_today()
    try:
        client = get_db()
        analytic_coll = client[scapp_database_name]['data_analytic']
        # buscamos el ultimo timestamp de hoy para ese centro
        ts = fetch_last_timestamp_for_centro(analytic_coll, centro_id, start_today)
        print(f"LRD: {ts=}")
        # si no hay para hoy, fallback al global
        if ts is None:
            ts = fetch_last_timestamp_overall(analytic_coll, centro_id, None )
            if ts is None:
                return jsonify({
                    'fecha': "Sin registros",
                    'online': False,
                    'upcoming': None
                }), 206
                # return jsonify({'error': 'No se encontraron datos'}), 404
        
        # determinamos si el timestamp cae en la fecha de hoy
        online = (ts.date() == datetime.now().date())

        # si esta online, agregamos 'upcoming' = updated + 1h
        if online:
            upcoming_ts = ts + timedelta(hours=1)
        else:
            upcoming_ts = None
        print(f"SCAPP-LRD Data generada")

        return jsonify({
            'fecha': ts.isoformat(),
            'online': online,
            'upcoming': upcoming_ts.isoformat()
        }), 200
    except Exception as e:
        print(f"No se ha obtenido el ultimo dato Scapp del centro {centro_id} por error en linea {e.__traceback__.tb_lineno}: \n{e}")
        return jsonify({"Error": "Datos no encontrados por servidor"}), 500

# —–– Funcion auxiliar: inicio del dia sin pytz —––––––––––––––
def get_start_of_today() -> datetime:
    """
    Devuelve un datetime (naive) con la fecha de hoy a las 00:00
    segun el reloj del sistema.
    """
    now = datetime.now()
    return datetime(year=now.year, month=now.month, day=now.day)

# —–– Funcion auxiliar: maximo timestamp para un centro_id —––––
def fetch_last_timestamp_for_centro(collection ,centro_id: str, start_time: datetime):
    """
    Retorna el timestamp mas reciente para el centro_id desde start_time.
    """
    pipeline = [
        {'$match': {
            'centro_id': centro_id,
            'updated': {'$gte': start_time}
        }},
        {'$sort':  {'updated': -1}},
        {'$limit': 1},
        {'$project': {'_id': 0, 'updated': 1}}
    ]
    docs = list(collection.aggregate(pipeline))
    print(f"LRD: {docs=}")
    return docs[0]['updated'] if docs else None

# —–– Funcion auxiliar: fallback al mas reciente global —–––––––
def fetch_last_timestamp_overall(collection, centro_id: str, start_time: datetime = None):
    """
    Si start_time esta dado, filtra >= start_time; si no, toma el mas reciente en toda la coleccion.
    """
    query = {
        'centro_id': centro_id,
        'updated': {'$exists': True}
    }
    # Si piden desde un tiempo de corte, lo añadimos al match
    if start_time is not None:
        query['updated']['$gte'] = start_time

    cursor = collection.find(query).sort('updated', -1).limit(1)
    doc = next(cursor, None)
    return doc['updated'] if doc else None

## ANALITICA POR CENTRO ##

@app.route('/scapp/get_analytic_center_data/<REGION>/<AREA>/<CENTRO>/<START_DAY>/<DAY_RANGES>', methods=['GET'] )
def scapp_get_analytic_center_data(CENTRO,AREA,REGION, START_DAY, DAY_RANGES):
    """
    Ruta de comunicacion con FE para obtener datos de analitica por centro y por dia, a partir de una fecha inicial y un rango de dias desde esa fecha
    TODO: Insertar ejemplo formato de entrada y salida
    
    """
    CENTRO = CENTRO.capitalize()
    AREA = AREA.capitalize()
    REGION = REGION.capitalize()
    fecha_str = START_DAY
    ranges_str = DAY_RANGES
    print(f"LDRd {fecha_str=}")
    print(f"LDRd {ranges_str=}")
    start_day = validar_y_convertir_fecha(fecha_str)
    day_ranges = validar_y_convertir_dias_desde(ranges_str)
    # print(f"LDRd {start_day=}")
    # print(f"LDRd {day_ranges=}")
    # if day_ranges is None or start_day is None:
    if not fecha_str:
        return jsonify({"Error": f"Parametros de dias en formato no valido-Se esperaba 'DD-MM-YYYY' y llego {fecha_str}"}), 400
    elif not ranges_str:
        return jsonify({"Error": f"Parametros de rango de dias en formato no valido: {day_ranges}"}), 400
        
    centro_id = f'{REGION}-{AREA}-{CENTRO}'

    scapp_configs = {}
    parse_data = True
        
    try:
        client = get_db()
        scapp_db = client[scapp_database_name]

        multiple_analytic_days = compute_multiple_analytic_days( centro_id, start_day, day_ranges, scapp_configs, parse_data)
        if multiple_analytic_days:
            print(f"Se ha generado exitosamente la analitica Scapp del centro {centro_id}")
            return jsonify({"analytic_data": multiple_analytic_days}), 200
        else:
            print(f"No hay informacion de analitica Scapp para el centro {centro_id}")
            return jsonify({"analytic_data":{}}), 200
    except Exception as e:
        print(f"No se ha generado correctamente a analitica Scapp del centro {centro_id} por error en linea {e.__traceback__.tb_lineno}: \n{e}")
        return jsonify({"Error": "Datos generados erroneamente por servidor"}), 500

@app.route('/scapp_get_centers_access', methods=['GET'])
@jwt_required()
def get_analytic_centers(): #USERNAME
    """
    Valida y retorna permisos de usuario para analitca Scapp
    """
    try:
        # username = str(USERNAME)
        username = get_jwt_identity()
        client = get_db()
        users_collection = client[user_database_name][user_database_name]

        available_centers = scapp_analytics_centers(users_collection, username)
    except Exception as e:
        print(f"No se han obtenido correctamente los permisos de analitica Scapp del usuario {username} por error en linea {e.__traceback__.tb_lineno}: \n{e}")
        return jsonify({"Error": f"No se han obtenido correctamente los permisos del Usuario {username}"}), 401
    if available_centers:
        return jsonify({"centers": available_centers}), 200
    else:
        return jsonify({"Error": f"Usuario {username} no posee permisos visualizar datos de centros"}), 404

def validar_y_convertir_fecha(fecha_str):
    """
    Verifica que la fecha venga en el formato correcto y la convierte al formato datetime. De fallar retorna None
    """
    try:
        print(f"{fecha_str=}")
        # Intentar parsear la fecha con el formato esperado
        fecha_dt = datetime.strptime(fecha_str, "%d-%m-%Y")
        # Retornar una nueva instancia de datetime con los valores correspondientes
        return datetime(fecha_dt.year, fecha_dt.month, fecha_dt.day)
    except Exception as e:
        # Si falla el formato, retorna None o lanza un error
        print(f"Formato de fecha invalido. Se esperaba 'DD-MM-YYYY' y llego {fecha_str}: {e.__traceback__.tb_lineno}:\n{e}")
        return None

def validar_y_convertir_dias_desde(valor):
    """
    Verifica que el rango de dias venga en el formato correcto y la convierte a entero. De fallar retorna None
    """
    try:
        range_days = int(valor)
        if range_days > 0:
            return range_days
        else:
            print(f"Error: Rango= {valor} menor a 1 para rango de dias.")
            return None
    except Exception as e:
        print(f"Formato erroneo para rango de dias {type(valor)}: {e.__traceback__.tb_lineno}:\n{e}")
        return None


@app.route('/scapp/compare_analytic_centers', methods=['GET'])
def compare_analytic_centers():
    """
    Ruta de comunicacion con FE para obtener datos de versus de analitica por centro y por dia, a partir de una fecha inicial y un rango de dias desde esa fecha
    
    _old: Recibe una lista de centro_id, fecha de inicio y rango y retorna los datos de analitica para multiples centros
    TODO: Insertar ejemplo formato de entrada y salida
    """
    # 1. Leer y validar parametros comunes
    centros_str = request.args.get('centros', '')
    fecha_str   = request.args.get('start_day')
    ranges_str  = request.args.get('day_ranges')
    # print(f"{centros_str=}")
    # print(f"{fecha_str=}")
    # print(f"{ranges_str=}")
    if not fecha_str or not ranges_str:
        return jsonify({"error": "Faltan parametros start_day o day_ranges"}), 400
    # 2. Parsear fecha en formato DD-MM-YYYY
    try:
        start_day  = validar_y_convertir_fecha(fecha_str)
        day_ranges = validar_y_convertir_dias_desde(ranges_str)
    except ValueError:
        return jsonify({
            "error": "El parametro start_day debe estar en formato DD-MM-YYYY, ej: 10-05-2025"
        }), 400

    # 3. Parsear lista de centros (entre 2 y 4)
    centros_list = [c.strip() for c in centros_str.split(',') if c.strip()]
    print(f"{centros_list=}")
    if len(centros_list) < 2 or len(centros_list) > 4:
        return jsonify({
            "error": "Debes especificar entre 2 y 4 centros, ej: ?centros=C1,C2,C3"
        }), 400

    # 4. Iterar y construir array de respuesta
    try:
        client         = get_db()
        scapp_db       = client[scapp_database_name]
        scapp_configs  = {}
        parse_data     = True

        response_list = []
        for centro_id in centros_list:
            # print(f"{centro_id=}")
            # print(f"{start_day=}")
            # print(f"{day_ranges=}")
            datos = compute_multiple_analytic_days(
                centro_id, start_day, day_ranges,
                scapp_configs, parse_data
            )
            response_list.append({
                "name": centro_id,
                "analytic_data": datos
            })

        # 5. Devolver un array JSON
        return jsonify(response_list), 200

    except Exception as e:
        linea = e.__traceback__.tb_lineno
        msg   = f"Error en linea {linea}: {e}"
        print(msg)
        return jsonify({"mensaje": msg}), 500


def scapp_analytics_centers(users_collection, username):
    """
    A partir del nombre de usuario, obtiene y retorna los permisos para ese usuario
    TODO: Insertar ejemplo formato de entrada y salida
    """
    permisos_usuario =[]
    permisos_centro = get_accesos_usuario(users_collection, username)
    
    for region in permisos_centro.keys():
        for area in permisos_centro[region]:
            for centro in permisos_centro[region][area]:
                status_scapp = permisos_centro[region][area][centro]['scapp']['status']
                if status_scapp:
                    permisos_usuario.append("-".join(reversed(centro.split("-"))).title())

    return permisos_usuario


## ANALITICA POR AREA ##

@app.route('/scapp/get_analytic_area_data/<REGION>/<AREA>/<START_DAY>/<DAY_RANGES>', methods=['GET'] )
def scapp_get_analytic_area_data(AREA, REGION, START_DAY, DAY_RANGES):
    """
    Ruta de comunicacion con FE para obtener datos de analitica por area y por dia, a partir de una fecha inicial y un rango de dias desde esa fecha
    _old: Recibe un area_id, fecha de inicio y rango de dias y retorna los datos de analitica del area solicitada por dia 

    TODO: Insertar ejemplo formato de entrada y salida
    """
    AREA = AREA.capitalize()
    REGION = REGION.capitalize()
    area_id = f'{REGION}-{AREA}'
    
    fecha_str = START_DAY
    ranges_str = DAY_RANGES
    
    start_day = validar_y_convertir_fecha(fecha_str)
    day_ranges = validar_y_convertir_dias_desde(ranges_str)
    
    # print(f"{area_id=}")
    if not start_day:
        return jsonify({"Error": f"Parametros de dias en formato no valido-Se esperaba 'DD-MM-YYYY' y llego {fecha_str}"}), 400
    elif not day_ranges:
        return jsonify({"Error": f"Parametros de rango de dias en formato no valido: {ranges_str}"}), 400
        
    
    scapp_configs = {}
    parse_data = True
        
    try:
        client = get_db()
        scapp_db = client[scapp_database_name]
        
        multiple_analytic_area_days = compute_analytic_area_data(area_id, start_day, day_ranges)
        if multiple_analytic_area_days:
            # print(f"{multiple_analytic_area_days=}")
            print(f"Se ha generado exitosamente la analitica Scapp del area {area_id}")
            return jsonify({"analytic_data": multiple_analytic_area_days}), 200
        else:
            print(f"No hay informacion de analitica Scapp para el area {area_id}")
            return jsonify({"analytic_data":{}}), 200
    except Exception as e:
        print(f"No se ha generado correctamente a analitica Scapp del area {area_id} por error en linea {e.__traceback__.tb_lineno}: \n{e}")
        return jsonify({"Error": "Datos generados erroneamente por servidor"}), 500

@app.route('/scapp/compare_analytic_areas', methods=['GET'])
def compare_analytic_areas():
    """
    Ruta de comunicacion con FE para obtener datos de versus de analitica por por area y por dia, a partir de una fecha inicial y un rango de dias desde esa fecha
    _old: Recibe una lista de areas_id, una fecha de inicio y rango de dias. Retorna los datos de analitica para multiples areas en los dias solicitados

    TODO: Insertar ejemplo formato de entrada y salida
    """
    # 1. Leer y validar parametros comunes
    areas_str = request.args.get('areas', '')
    fecha_str   = request.args.get('start_day')
    ranges_str  = request.args.get('day_ranges')
    # print(f"{areas_str=}")
    # print(f"{fecha_str=}")
    # print(f"{ranges_str=}")
    start_day  = validar_y_convertir_fecha(fecha_str)
    day_ranges = validar_y_convertir_dias_desde(ranges_str)

    if not start_day:
        return jsonify({"Error": f"Parametros de dias en formato no valido-Se esperaba 'DD-MM-YYYY' y se recibio {fecha_str}"}), 400
    elif not day_ranges:
        return jsonify({"Error": f"Parametros de rango de dias en formato no valido: {ranges_str}"}), 400


    # 2. Parsear lista de centros (entre 2 y 4)
    areas_list = [c.strip() for c in areas_str.split(',') if c.strip()]
    # print(f"{areas_list=}")
    if len(areas_list) < 2 or len(areas_list) > 4:
        return jsonify({
            "Error": "Debes especificar entre 2 y 4 areas, ej: ?areas=A1,A2,A3"
        }), 400

    # 3. Iterar y construir array de respuesta
    try:
        client         = get_db()
        scapp_db       = client[scapp_database_name]
        scapp_configs  = {}
        parse_data     = True
        collection = scapp_db["data_analytic"]

        response_list = []
        for area_id in areas_list:
            print(f"{area_id=}")
            print(f"{start_day=}")
            print(f"{day_ranges=}")
            
            datos= compute_analytic_area_versus_data(collection, area_id, start_day, day_ranges)
            # print(f"centro {area_id}, datos:  {datos}")
            if datos:
                print(f"Hay datos en {area_id}")
            else:
                print(f"Sin datos en {area_id}")
                
            response_list.append({
                "name": area_id,
                "analytic_data": datos
            })
        # print(response_list)
        # 4. Devolver un array JSON
        return jsonify(response_list), 200

    except Exception as e:
        linea = e.__traceback__.tb_lineno
        msg   = f"Error en linea {linea}: {e}"
        print(msg)
        return jsonify({"Error": msg}), 500

## ANALITICA POR REGION ##

@app.route('/scapp/get_analytic_region_data/<REGION>/<START_DAY>/<DAY_RANGES>', methods=['GET'] )
# @jwt_required()
def scapp_get_analytic_region_data(REGION, START_DAY, DAY_RANGES):
    """
    Ruta de comunicacion con FE para obtener datos de analitica por region y por dia, a partir de una fecha inicial y un rango de dias desde esa fecha
    TODO: Insertar ejemplo formato de entrada y salida
    """
    # username = get_jwt_identity()
    client = get_db()

    # user_access = region_analytics_access(client, username,  )
    # if not user_access:
    #     return jsonify({"Error": f"EL usuario {username} no posee permiso para visualizar la Analítica de Región"}), 401


    REGION = REGION.capitalize()
    region_id = f'{REGION}'
    
    fecha_str = START_DAY
    ranges_str = DAY_RANGES
    
    start_day = validar_y_convertir_fecha(fecha_str)
    day_ranges = validar_y_convertir_dias_desde(ranges_str)
    

    if not start_day:
        return jsonify({"Error": f"Parametros de dias en formato no valido-Se esperaba 'DD-MM-YYYY' y se recibio {fecha_str}"}), 400
    elif not day_ranges:
        return jsonify({"Error": f"Parametros de rango de dias en formato no valido: {ranges_str}"}), 400
        
    
    scapp_configs = {}
    parse_data = True
    
    try:
        
        scapp_db = client[scapp_database_name]
        
        multiple_analytic_region_days = compute_analytics_by_region(scapp_db, region_id, start_day, day_ranges)
        if multiple_analytic_region_days:
            print(f"Se ha generado exitosamente la analitica Scapp de la Region {region_id}")
            return jsonify( multiple_analytic_region_days), 200
        else:
            print(f"No hay informacion de analitica Scapp de la Region {region_id}")
            return jsonify({"analytic_data":{}}), 200
    except Exception as e:
        print(f"No se ha generado correctamente a analitica Scapp de la Region {region_id} por error en linea {e.__traceback__.tb_lineno}: \n{e}")
        return jsonify({"Error": "Datos generados erroneamente por servidor"}), 500

def region_analytics_access(db, username, region_analytic_access):
    users_collection = db[user_database_name].users

    #Construccion de Query para obtener cargo de usuario
    proyeccion = {"_id": 0, "password": 0, "refresh_token": 0, "access_token": 0}
    usuario_actual_info = users_collection.find_one({'username': username}, proyeccion)
    cargo_actual = usuario_actual_info['cargo']
    #Condicion: solo Gerencia puede acceder a analitica de region
    if cargo_actual and cargo_actual in region_analytic_access:
        return True
    else:
        return False

@app.teardown_request
def close_db_request(exc=None):
    """Se ejecuta tras cada request, incluso si hubo excepcion."""
    db_client = g.pop('db_client', None)
    if db_client:
        db_client.close()

@app.teardown_appcontext
def close_db_appcontext(exc=None):
    """Se ejecuta al destruir el application context."""
    db_client = g.pop('db_client', None)
    if db_client:
        db_client.close()

# @app.teardown_appcontext
# @app.teardown_request
# def close_db(exc=None):
#     """Cierra la conexion a MongoDB despues de cada request."""
#     db_client = g.pop('db_client', None)  # Extrae y elimina la conexion del contexto global
#     if db_client is not None:
#         db_client.close()  # Cierra la conexion solo si existia
        
if __name__ == '__main__':
    
    debug = False
    # # host = '172.16.1.88'
    # host = '192.168.20.145'
    # host = '127.0.0.1'
    host = '0.0.0.0'
    port = 4024
           
    print("Version ITGServiceManager g.db 10-10-2025")
    # app.run()
    # app.run(host=host, port=port, debug=debug)
    with app.app_context():
        app.run(host=host, port=port, debug=debug, use_reloader=True, threaded=True)