CoolFace
Apppublic

fmatituy/selectospromanager

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
queries.py716 linesDownload Raw Back to database
1import sqlite32import os3from database.db import DB_PATH, hash_password4 5def db_execute(query, params=()):6    try:7        conn = sqlite3.connect(DB_PATH)8        c = conn.cursor()9        c.execute(query, params)10        conn.commit()11        conn.close()12    except Exception as e:13        print(f"!!! DB EXECUTE ERROR: {e}")14        print(f"QUERY: {query}")15        print(f"PARAMS: {params}")16        raise e17 18def get_database_summary():19    """Genera un resumen analítico de toda la base de datos para SARA IA."""20    summary = {}21    try:22        summary['total_proyectos'] = db_fetchone("SELECT COUNT(*) as c FROM proyectos")['c']23        summary['proyectos_activos'] = db_fetchone("SELECT COUNT(*) as c FROM proyectos WHERE estado = 'Activo'")['c']24        summary['permisos_pendientes'] = db_fetchone("SELECT COUNT(*) as c FROM sst_solicitudes_permisos WHERE estado = 'Pendiente'")['c']25        summary['permisos_emitidos'] = db_fetchone("SELECT COUNT(*) as c FROM formatos_sst")['c']26        summary['total_empleados'] = db_fetchone("SELECT COUNT(*) as c FROM empleados WHERE estado = 'Activo'")['c']27        28        # Últimos 3 permisos generados para contexto rápido29        summary['ultimos_permisos'] = db_fetchall("SELECT consecutivo, tipo_formato, creado_por FROM formatos_sst ORDER BY id DESC LIMIT 3")30        31        # Últimas 5 solicitudes de todos los proyectos32        summary['solicitudes_recientes'] = db_fetchall("""33            SELECT s.consecutivo, p.nombre as proyecto, s.detalles34            FROM sst_solicitudes_permisos s35            JOIN proyectos p ON s.proyecto_id = p.id36            WHERE s.estado = 'Pendiente'37            ORDER BY s.id DESC LIMIT 538        """)39    except Exception as e:40        summary['error'] = str(e)41    return summary42 43def db_fetchall(query, params=()):44    conn = sqlite3.connect(DB_PATH)45    conn.row_factory = sqlite3.Row46    c = conn.cursor()47    c.execute(query, params)48    res = [dict(row) for row in c.fetchall()]49    conn.close()50    return res51 52def db_fetchone(query, params=()):53    conn = sqlite3.connect(DB_PATH)54    conn.row_factory = sqlite3.Row55    c = conn.cursor()56    c.execute(query, params)57    res = c.fetchone()58    conn.close()59    return dict(res) if res else None60 61def get_all_roles():62    return db_fetchall("SELECT id, nombre FROM roles")63 64def create_user(username, password_hash, rol_id, nombre):65    try:66        db_execute(67            "INSERT INTO usuarios (username, password, rol_id, nombre) VALUES (?, ?, ?, ?)",68            (username, password_hash, rol_id, nombre)69        )70        return True71    except Exception as e:72        print(f"Error creating user: {e}")73        return None74 75def get_projects():76    return db_fetchall("""77        SELECT p.*, COALESCE(u.nombre, p.cliente_nombre) as cliente_nombre 78        FROM proyectos p 79        LEFT JOIN usuarios u ON p.cliente_id = u.id80        ORDER BY p.id DESC81    """)82 83def get_clients():84    return db_fetchall("""85        SELECT u.id, u.nombre, u.username, r.nombre as rol 86        FROM usuarios u 87        JOIN roles r ON u.rol_id = r.id 88        WHERE r.nombre = 'Cliente'89    """)90 91 92def get_certificaciones():93    return db_fetchall("""94        SELECT c.*, e.nombre as empleado 95        FROM sst_certificaciones c96        JOIN empleados e ON c.usuario_id = e.id97        ORDER BY c.fecha_vencimiento98    """)99 100def get_all_employees():101    """Retorna todos los registros de la base de nómina (empleados)"""102    return db_fetchall("SELECT * FROM empleados ORDER BY nombre")103 104def get_dropdown_employees():105    """Versión optimizada para selectores (dropdowns) de personal"""106    return db_fetchall("SELECT id, nombre, cargo, area, documento, firma_url FROM empleados WHERE estado = 'Activo' ORDER BY nombre")107 108def get_actividades_pendientes_sst():109    return db_fetchall("SELECT * FROM actividades_proyecto WHERE estado != 'Completado'")110 111def get_solicitudes_permiso(usuario_id=None):112    if usuario_id:113        return db_fetchall("""114            SELECT s.*, 115                   COALESCE(e.nombre, u.nombre, 'Sistema/Auto') as solicitante,116                   COALESCE(e.cargo, r.nombre, 'Administrativo') as solicitante_cargo,117                   p.nombre as proyecto, p.nombre as proyecto_nombre118            FROM sst_solicitudes_permisos s119            LEFT JOIN empleados e ON s.solicitante_id = e.id120            LEFT JOIN usuarios u ON s.solicitante_id = u.id121            LEFT JOIN roles r ON u.rol_id = r.id122            JOIN proyectos p ON s.proyecto_id = p.id123            WHERE s.solicitante_id = ? AND s.estado = 'Pendiente'124            ORDER BY s.id DESC125        """, (usuario_id,))126    return db_fetchall("""127        SELECT s.*, 128               COALESCE(e.nombre, u.nombre, 'Sistema/Auto') as solicitante,129               COALESCE(e.cargo, r.nombre, 'Administrativo') as solicitante_cargo,130               p.nombre as proyecto, p.nombre as proyecto_nombre131        FROM sst_solicitudes_permisos s132        LEFT JOIN empleados e ON s.solicitante_id = e.id133        LEFT JOIN usuarios u ON s.solicitante_id = u.id134        LEFT JOIN roles r ON u.rol_id = r.id135        JOIN proyectos p ON s.proyecto_id = p.id136        WHERE s.estado = 'Pendiente'137        ORDER BY s.id DESC138    """)139 140def get_certificaciones_usuario(usuario_id):141    return db_fetchall("SELECT * FROM sst_certificaciones WHERE usuario_id = ?", (usuario_id,))142 143def get_actividades_por_proyecto(proyecto_id):144    return db_fetchall("SELECT * FROM actividades_proyecto WHERE proyecto_id = ?", (proyecto_id,))145 146def get_proyecto_by_id(proyecto_id):147    return db_fetchone("SELECT * FROM proyectos WHERE id = ?", (proyecto_id,))148 149def get_proyecto_detallado(proyecto_id):150    return db_fetchone("""151        SELECT p.*, 152               u_dir.nombre as director_nombre,153               u_res.nombre as residente_nombre,154               u_sup.nombre as supervisor_nombre,155               u_sst.nombre as responsable_sst_nombre, 156               u_log.nombre as logistica_nombre,157               COALESCE(u_cliente.nombre, p.cliente_nombre) as cliente_nombre158        FROM proyectos p159        LEFT JOIN empleados u_dir ON p.director_id = u_dir.id160        LEFT JOIN empleados u_res ON p.residente_id = u_res.id161        LEFT JOIN empleados u_sup ON p.supervisor_id = u_sup.id162        LEFT JOIN empleados u_sst ON p.sst_id = u_sst.id163        LEFT JOIN empleados u_log ON p.logistica_id = u_log.id164        LEFT JOIN usuarios u_cliente ON p.cliente_id = u_cliente.id165        WHERE p.id = ?166    """, (proyecto_id,))167 168def get_proyecto_by_token(token_cliente):169    return db_fetchone("""170        SELECT p.*, 171               u_dir.nombre as director_nombre,172               u_res.nombre as residente_nombre,173               u_sup.nombre as supervisor_nombre,174               u_sst.nombre as responsable_sst_nombre, 175               COALESCE(u_cliente.nombre, p.cliente_nombre) as cliente_nombre176        FROM proyectos p177        LEFT JOIN empleados u_dir ON p.director_id = u_dir.id178        LEFT JOIN empleados u_res ON p.residente_id = u_res.id179        LEFT JOIN empleados u_sup ON p.supervisor_id = u_sup.id180        LEFT JOIN empleados u_sst ON p.sst_id = u_sst.id181        LEFT JOIN usuarios u_cliente ON p.cliente_id = u_cliente.id182        WHERE p.token_cliente = ?183    """, (token_cliente,))184 185def update_proyecto_token(proyecto_id, token):186    db_execute("UPDATE proyectos SET token_cliente = ? WHERE id = ?", (token, proyecto_id))187 188def get_actividades_por_proyecto_detallado(proyecto_id):189    """Obtiene actividades con JOINs flexibles para asegurar la carga completa."""190    return db_fetchall("""191        SELECT a.*, 192               COALESCE(u_lid.nombre, 'Sin asignar') as responsable_nombre, 193               COALESCE(u_sst.nombre, 'Sin asignar') as responsable_sst_nombre194        FROM actividades_proyecto a195        LEFT JOIN empleados u_lid ON a.responsable_id = u_lid.id196        LEFT JOIN empleados u_sst ON a.responsable_sst_id = u_sst.id197        WHERE a.proyecto_id = ?198    """, (proyecto_id,))199 200def get_occupied_employees_by_date(fecha):201    """Retorna un conjunto de IDs de empleados ocupados en una fecha dada."""202    # Buscamos en todas las actividades de todos los proyectos que empiecen en esa fecha203    # (Podríamos expandir esto a rangos si tuviéramos fecha_fin confiable)204    acts = db_fetchall("SELECT responsable_id, responsable_sst_id, personal_operativo_json FROM actividades_proyecto WHERE fecha_inicio = ?", (fecha,))205    import json206    occupied = set()207    for a in acts:208        if a['responsable_id']: occupied.add(a['responsable_id'])209        if a['responsable_sst_id']: occupied.add(a['responsable_sst_id'])210        if a['personal_operativo_json']:211            try:212                op_ids = json.loads(a['personal_operativo_json'])213                if isinstance(op_ids, list):214                    for oid in op_ids: occupied.add(oid)215            except: pass216    return occupied217 218def get_user_by_id(user_id):219    return db_fetchone("SELECT * FROM usuarios WHERE id = ?", (user_id,))220 221def get_todas_certificaciones_activos():222    return db_fetchall("SELECT * FROM sst_certificaciones")223 224def create_certificacion(usuario_id, tipo, fecha_emision, fecha_vencimiento, entidad, archivo_url=""):225    db_execute("INSERT INTO sst_certificaciones (usuario_id, tipo, fecha_emision, fecha_vencimiento, entidad, archivo_url) VALUES (?, ?, ?, ?, ?, ?)",226               (usuario_id, tipo, fecha_emision, fecha_vencimiento, entidad, archivo_url))227 228def get_certificaciones_activo(usuario_id):229    return db_fetchall("SELECT * FROM sst_certificaciones WHERE usuario_id = ?", (usuario_id,))230 231def get_herramientas():232    return db_fetchall("""233        SELECT h.*, c.nombre as categoria_nombre234        FROM inventario_herramientas h235        LEFT JOIN inv_categorias c ON h.categoria_id = c.id236    """)237 238def get_materiales():239    return db_fetchall("SELECT * FROM inventario_materiales")240 241def create_herramienta(nombre, marca, modelo, serial, estado, responsable, ubicacion, fecha_compra, garantia, categoria_id=None, foto_url=None):242    db_execute("""243        INSERT INTO inventario_herramientas 244        (nombre, marca, modelo, serial, estado, responsable, ubicacion, fecha_compra, garantia, categoria_id, foto_url) 245        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)246    """, (nombre, marca, modelo, serial, estado, responsable, ubicacion, fecha_compra, garantia, categoria_id, foto_url))247 248def update_herramienta(h_id, data):249    """Actualiza una herramienta de forma dinámica."""250    fields = []251    values = []252    for k, v in data.items():253        fields.append(f"{k} = ?")254        values.append(v)255    values.append(h_id)256    query = f"UPDATE inventario_herramientas SET {', '.join(fields)} WHERE id = ?"257    db_execute(query, tuple(values))258 259def delete_herramienta(h_id):260    db_execute("DELETE FROM inventario_herramientas WHERE id = ?", (h_id,))261 262def get_hoja_vida(item_id, item_tipo):263    return db_fetchall("""264        SELECT hv.*, u.nombre as usuario_nombre265        FROM inv_hoja_vida hv266        LEFT JOIN usuarios u ON hv.usuario_id = u.id267        WHERE hv.item_id = ? AND hv.item_tipo = ?268        ORDER BY hv.fecha DESC269    """, (item_id, item_tipo))270 271def add_hoja_vida_entry(item_id, item_tipo, evento, descripcion, icon, color, usuario_id, fecha=None):272    from datetime import datetime273    if not fecha:274        fecha = datetime.now().strftime('%Y-%m-%d %H:%M')275    db_execute("""276        INSERT INTO inv_hoja_vida (item_id, item_tipo, fecha, evento, descripcion, icon, color, usuario_id)277        VALUES (?, ?, ?, ?, ?, ?, ?, ?)278    """, (item_id, item_tipo, fecha, evento, descripcion, icon, color, usuario_id))279 280def add_material(nombre, cantidad, unidad):281    # Check if exists282    existing = db_fetchone("SELECT id, cantidad FROM inventario_materiales WHERE nombre = ?", (nombre,))283    if existing:284        db_execute("UPDATE inventario_materiales SET cantidad = cantidad + ? WHERE id = ?", (cantidad, existing['id']))285    else:286        db_execute("INSERT INTO inventario_materiales (nombre, cantidad, unidad) VALUES (?, ?, ?)", (nombre, cantidad, unidad))287 288# --- NUEVOS QUERIES PARA INVENTARIO PRO ---289 290def get_inv_categorias():291    return db_fetchall("SELECT * FROM inv_categorias ORDER BY nombre")292 293def create_inv_categoria(nombre, descripcion, tipo_control='Stock'):294    db_execute("INSERT INTO inv_categorias (nombre, descripcion, tipo_control) VALUES (?, ?, ?)", (nombre, descripcion, tipo_control))295 296def delete_inv_categoria(cat_id):297    db_execute("DELETE FROM inv_categorias WHERE id = ?", (cat_id,))298 299def get_inv_movimientos(limit=100):300    return db_fetchall("""301        SELECT m.*, u.nombre as usuario_nombre302        FROM inv_movimientos m303        JOIN usuarios u ON m.usuario_id = u.id304        ORDER BY m.fecha DESC305        LIMIT ?306    """, (limit,))307 308def register_inv_movimiento(item_id, item_tipo, tipo_mov, cantidad, origen, destino, usuario_id, notas=""):309    from datetime import datetime310    db_execute("""311        INSERT INTO inv_movimientos (item_id, item_tipo, tipo_movimiento, cantidad, origen, destino, usuario_id, fecha, notas)312        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)313    """, (item_id, item_tipo, tipo_mov, cantidad, origen, destino, usuario_id, datetime.now().strftime('%Y-%m-%d %H:%M'), notas))314 315def get_ordenes_trabajo():316    return db_fetchall("""317        SELECT ot.*, p.nombre as proyecto_nombre, u.nombre as tecnico_nombre318        FROM inv_ordenes_trabajo ot319        JOIN proyectos p ON ot.proyecto_id = p.id320        JOIN usuarios u ON ot.tecnico_id = u.id321        ORDER BY ot.fecha_apertura DESC322    """)323 324def create_ot(codigo, proyecto_id, tecnico_id, descripcion):325    from datetime import datetime326    db_execute("""327        INSERT INTO inv_ordenes_trabajo (codigo_ot, proyecto_id, tecnico_id, descripcion_trabajo, fecha_apertura)328        VALUES (?, ?, ?, ?, ?)329    """, (codigo, proyecto_id, tecnico_id, descripcion, datetime.now().strftime('%Y-%m-%d %H:%M')))330 331def assign_item_to_ot(ot_id, item_id, item_tipo, cantidad):332    from datetime import datetime333    db_execute("""334        INSERT INTO inv_asignaciones_ot (ot_id, item_id, item_tipo, cantidad_solicitada, fecha_asignacion)335        VALUES (?, ?, ?, ?, ?)336    """, (ot_id, item_id, item_tipo, cantidad, datetime.now().strftime('%Y-%m-%d %H:%M')))337 338def update_actividad_estado(act_id, nuevo_estado):339    db_execute("UPDATE actividades_proyecto SET estado = ? WHERE id = ?", (nuevo_estado, act_id))340 341def update_estado_solicitud_permiso(sol_id, nuevo_estado, comentario=""):342    db_execute("UPDATE sst_solicitudes_permisos SET estado = ?, comentarios = ? WHERE id = ?", (nuevo_estado, comentario, sol_id))343 344def create_actividad_proyecto(proyecto_id, nombre, descripcion, tipo_actividad, prioridad, duracion, fecha_inicio, responsable_id, responsable_sst_id, personal_json, requiere_aro=0, requiere_jsa=0, requiere_permiso=0, tipos_permiso_json='[]', hora_inicio='07:00', hora_fin='17:00', solicitante_id=None):345    conn = sqlite3.connect(DB_PATH)346    c = conn.cursor()347    c.execute("""348        INSERT INTO actividades_proyecto 349        (proyecto_id, nombre, descripcion, tipo_actividad, prioridad, duracion_estimada, fecha_inicio, responsable_id, responsable_sst_id, personal_operativo_json, requiere_aro, requiere_jsa, requiere_permiso, tipos_permiso_json, hora_inicio, hora_fin, estado) 350        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'Pendiente')351    """, (proyecto_id, nombre, descripcion, tipo_actividad, prioridad, duracion, fecha_inicio, responsable_id, responsable_sst_id, personal_json, requiere_aro, requiere_jsa, requiere_permiso, tipos_permiso_json, hora_inicio, hora_fin))352    353    act_id = c.lastrowid354    355    # SI REQUIERE CUALQUIER FORMATO SST (ARO, JSA o PERMISO) -> CREAR SOLICITUD AUTOMÁTICA356    if requiere_aro or requiere_jsa or requiere_permiso:357        from datetime import datetime358        fecha_sol = datetime.now().strftime('%Y-%m-%d %H:%M')359        360        formatos = []361        if requiere_aro: formatos.append("ARO")362        if requiere_jsa: formatos.append("JSA")363        if requiere_permiso: formatos.append(f"Permiso Alto Riesgo ({tipos_permiso_json})")364        365        detalles_sol = f"Solicitud automática desde Cronograma para: {nombre}. Formatos requeridos: {', '.join(formatos)}"366        367        # El solicitante_id es quien está creando la actividad (usuario logueado)368        # Fallback al responsable de la actividad o ID 1369        final_sol_id = solicitante_id or responsable_id or 1370        371        # Generar Consecutivo de Petición (Reducido)372        res_pet = db_fetchone("SELECT MAX(id) as last_id FROM sst_solicitudes_permisos")373        next_pet = (res_pet['last_id'] or 0) + 1374        consecutivo_pet = f"PET-{next_pet:03d}"375 376        c.execute("""377            INSERT INTO sst_solicitudes_permisos (proyecto_id, solicitante_id, responsable_sst_id, fecha_solicitud, estado, detalles, consecutivo, actividad_id)378            VALUES (?, ?, ?, ?, ?, ?, ?, ?)379        """, (proyecto_id, final_sol_id, responsable_sst_id or 1, fecha_sol, 'Pendiente', detalles_sol, consecutivo_pet, act_id))380        381    conn.commit()382    conn.close()383    return act_id384 385def get_bitacora_proyecto(proyecto_id):386    return db_fetchall("SELECT * FROM proyectos_bitacora WHERE proyecto_id = ? ORDER BY fecha DESC", (proyecto_id,))387 388def create_bitacora_entry(data):389    db_execute("""390        INSERT INTO proyectos_bitacora (proyecto_id, usuario_id, fecha, actividades_realizadas, personal_json, materiales_json, herramientas_json, clima, incidentes, fotos_json)391        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)392    """, (data['proyecto_id'], data['usuario_id'], data['fecha'], data['actividades'], data['personal'], data['materiales'], data['herramientas'], data['clima'], data['incidentes'], data['fotos']))393 394def get_documentos_proyecto(proyecto_id):395    return db_fetchall("SELECT * FROM proyectos_documentos WHERE proyecto_id = ? ORDER BY fecha_subida DESC", (proyecto_id,))396 397def add_documento_proyecto(proyecto_id, nombre, tipo, url, usuario_id):398    from datetime import datetime399    db_execute("""400        INSERT INTO proyectos_documentos (proyecto_id, nombre, tipo, url, usuario_id, fecha_subida)401        VALUES (?, ?, ?, ?, ?, ?)402    """, (proyecto_id, nombre, tipo, url, usuario_id, datetime.now().strftime('%Y-%m-%d %H:%M')))403 404def create_proyecto(data):405    conn = sqlite3.connect(DB_PATH)406    c = conn.cursor()407    c.execute("""408        INSERT INTO proyectos (409            nombre, descripcion, estado, fecha_inicio, fecha_fin, cliente_id, cliente_nombre, 410            tipo_proyecto, ubicacion, director_id, residente_id, supervisor_id, sst_id, logistica_id, avance411        )412        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)413    """, (414        data['nombre'], data.get('descripcion', ''), data['estado'], data['fecha_inicio'], data.get('fecha_fin'), data.get('cliente_id'), data.get('cliente_nombre'),415        data.get('tipo_proyecto'), data.get('ubicacion'), data.get('director_id'), data.get('residente_id'), 416        data.get('supervisor_id'), data.get('sst_id'), data.get('logistica_id')417    ))418    last_id = c.lastrowid419    conn.commit()420    conn.close()421    return last_id422 423def update_proyecto(proyecto_id, data):424    """Actualiza los datos de un proyecto de forma dinámica."""425    fields = []426    values = []427    for k, v in data.items():428        fields.append(f"{k} = ?")429        values.append(v)430    431    values.append(proyecto_id)432    query = f"UPDATE proyectos SET {', '.join(fields)} WHERE id = ?"433    db_execute(query, tuple(values))434 435def update_certificacion_personal(cert_id, data):436    fields = []437    values = []438    for k, v in data.items():439        fields.append(f"{k} = ?")440        values.append(v)441    values.append(cert_id)442    query = f"UPDATE sst_certificaciones SET {', '.join(fields)} WHERE id = ?"443    db_execute(query, tuple(values))444 445def update_certificacion_equipo(cert_id, data):446    fields = []447    values = []448    for k, v in data.items():449        fields.append(f"{k} = ?")450        values.append(v)451    values.append(cert_id)452    query = f"UPDATE sst_certificaciones SET {', '.join(fields)} WHERE id = ?"453    db_execute(query, tuple(values))454 455def update_actividad_proyecto(act_id, data):456    """Actualiza una actividad de forma dinámica."""457    fields = []458    values = []459    for k, v in data.items():460        fields.append(f"{k} = ?")461        values.append(v)462    values.append(act_id)463    query = f"UPDATE actividades_proyecto SET {', '.join(fields)} WHERE id = ?"464    db_execute(query, tuple(values))465 466def update_actividad_evidencias(act_id, evidencias_json):467    db_execute("UPDATE actividades_proyecto SET evidencias_json = ? WHERE id = ?", (evidencias_json, act_id))468 469def update_actividad_documentos(act_id, documentos_json):470    db_execute("UPDATE actividades_proyecto SET documentos_legales_json = ? WHERE id = ?", (documentos_json, act_id))471 472def get_actividad_by_id(act_id):473    return db_fetchone("SELECT * FROM actividades_proyecto WHERE id = ?", (act_id,))474 475def update_proyecto_avance(proyecto_id, avance):476    db_execute("UPDATE proyectos SET avance = ? WHERE id = ?", (avance, proyecto_id))477 478def get_chat_history_by_role(rol_nombre, limit=10):479    """Obtiene los últimos N mensajes del chat para un rol específico."""480    return db_fetchall("""481        SELECT mensaje_usuario, respuesta_ia 482        FROM sst_chat_history 483        WHERE rol_nombre = ? 484        ORDER BY id DESC LIMIT ?485    """, (rol_nombre, limit))486 487def save_chat_message(rol_nombre, usuario_id, mensaje_usuario, respuesta_ia):488    """Guarda un intercambio del chat en la base de datos."""489    db_execute("""490        INSERT INTO sst_chat_history (rol_nombre, usuario_id, mensaje_usuario, respuesta_ia)491        VALUES (?, ?, ?, ?)492    """, (rol_nombre, usuario_id or 1, mensaje_usuario, respuesta_ia))493# --- QUERIES EXPERTOS: MATERIALES Y CONSUMIBLES ---494 495def get_inv_materiales_pro():496    return db_fetchall("""497        SELECT m.*, 498               t.nombre as tipo_nombre, 499               c.nombre as categoria_nombre, 500               u.abreviatura as unidad_nombre,501               ub.nombre as ubicacion_nombre502        FROM inv_materiales_pro m503        LEFT JOIN inv_tipos_material t ON m.tipo_id = t.id504        LEFT JOIN inv_categorias c ON m.categoria_id = c.id505        LEFT JOIN inv_unidades u ON m.unidad_id = u.id506        LEFT JOIN inv_ubicaciones ub ON m.ubicacion_id = ub.id507        ORDER BY m.nombre ASC508    """)509 510def get_inv_tipos_material():511    return db_fetchall("SELECT * FROM inv_tipos_material WHERE estado = 'Activo' ORDER BY nombre")512 513def create_inv_tipo_material(nombre):514    db_execute("INSERT INTO inv_tipos_material (nombre) VALUES (?)", (nombre,))515 516def delete_inv_tipo_material(tipo_id):517    db_execute("DELETE FROM inv_tipos_material WHERE id = ?", (tipo_id,))518 519def get_inv_unidades():520    return db_fetchall("SELECT * FROM inv_unidades ORDER BY nombre")521 522def create_inv_unidad(nombre, abreviatura):523    db_execute("INSERT INTO inv_unidades (nombre, abreviatura) VALUES (?, ?)", (nombre, abreviatura))524 525def delete_inv_unidad(und_id):526    db_execute("DELETE FROM inv_unidades WHERE id = ?", (und_id,))527 528def get_inv_ubicaciones():529    return db_fetchall("SELECT * FROM inv_ubicaciones ORDER BY nombre")530 531def create_inv_ubicacion(nombre, tipo, responsable, descripcion=""):532    db_execute("INSERT INTO inv_ubicaciones (nombre, tipo, responsable, descripcion) VALUES (?, ?, ?, ?)", (nombre, tipo, responsable, descripcion))533 534def delete_inv_ubicacion(ubi_id):535    db_execute("DELETE FROM inv_ubicaciones WHERE id = ?", (ubi_id,))536 537def create_inv_material_pro(data):538    from datetime import datetime539    db_execute("""540        INSERT INTO inv_materiales_pro 541        (codigo, nombre, tipo_id, categoria_id, unidad_id, stock_actual, stock_minimo, 542         ubicacion_id, proveedor_principal, precio_unitario, observaciones, fecha_registro) 543        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)544    """, (545        data['codigo'], data['nombre'], data['tipo_id'], data['categoria_id'], 546        data['unidad_id'], data['stock_actual'], data['stock_minimo'], 547        data['ubicacion_id'], data['proveedor_principal'], data['precio_unitario'], 548        data['observaciones'], datetime.now().strftime('%Y-%m-%d %H:%M')549    ))550 551def update_inv_stock_material(material_id, cantidad, tipo_mov, usuario_id, notas=""):552    from datetime import datetime553    # Update stock554    op = "+" if tipo_mov in ['Entrada', 'Ajuste Positivo'] else "-"555    db_execute(f"UPDATE inv_materiales_pro SET stock_actual = stock_actual {op} ?, ultimo_movimiento = ? WHERE id = ?", 556               (cantidad, datetime.now().strftime('%Y-%m-%d %H:%M'), material_id))557    558    # Register movement559    register_inv_movimiento(material_id, 'Material', tipo_mov, cantidad, 'Ajuste', 'Bodega', usuario_id, notas)560 561def get_inv_materiales_alertas():562    critico = db_fetchall("SELECT COUNT(*) as count FROM inv_materiales_pro WHERE stock_actual <= (stock_minimo * 0.5)")563    bajo = db_fetchall("SELECT COUNT(*) as count FROM inv_materiales_pro WHERE stock_actual <= stock_minimo AND stock_actual > (stock_minimo * 0.5)")564    recientes = db_fetchall("SELECT COUNT(*) as count FROM inv_movimientos WHERE tipo_movimiento = 'Entrada' AND fecha >= date('now')")565    566    return {567        'critico': critico[0]['count'] if critico else 0,568        'bajo': bajo[0]['count'] if bajo else 0,569        'recientes': recientes[0]['count'] if recientes else 0570    }571 572# --- NUEVOS QUERIES PARA CRM CORPORATIVO ---573def get_crm_clientes():574    return db_fetchall("""575        SELECT c.*, COUNT(p.id) as proyectos_count576        FROM crm_clientes c577        LEFT JOIN proyectos p ON c.id = p.cliente_id578        GROUP BY c.id579        ORDER BY c.nombre ASC580    """)581 582def get_crm_cliente_by_id(client_id):583    return db_fetchone("SELECT * FROM crm_clientes WHERE id = ?", (client_id,))584 585def create_crm_cliente(data):586    from datetime import datetime587    db_execute("""588        INSERT INTO crm_clientes 589        (nit, nombre, tipo, industria, contacto_principal, telefono, email, direccion, ciudad, notas, fecha_creacion)590        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)591    """, (592        data.get('nit', ''), data['nombre'], data.get('tipo', 'B2B'), data.get('industria', ''),593        data.get('contacto_principal', ''), data.get('telefono', ''), data.get('email', ''), 594        data.get('direccion', ''), data.get('ciudad', ''), data.get('notas', ''), datetime.now().strftime('%Y-%m-%d %H:%M')595    ))596 597def get_crm_oportunidades():598    return db_fetchall("""599        SELECT o.*, c.nombre as cliente_nombre, u.nombre as responsable_nombre600        FROM crm_oportunidades o601        JOIN crm_clientes c ON o.cliente_id = c.id602        LEFT JOIN usuarios u ON o.responsable_id = u.id603        ORDER BY o.fecha_creacion DESC604    """)605 606def create_crm_oportunidad(data):607    from datetime import datetime608    db_execute("""609        INSERT INTO crm_oportunidades610        (cliente_id, titulo, valor, etapa, fecha_cierre, probabilidad, responsable_id, notas, fecha_creacion)611        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)612    """, (613        data['cliente_id'], data['titulo'], data.get('valor', 0.0), data.get('etapa', 'Prospección'),614        data.get('fecha_cierre', ''), data.get('probabilidad', 50), data.get('responsable_id'),615        data.get('notas', ''), datetime.now().strftime('%Y-%m-%d %H:%M')616    ))617 618def update_crm_oportunidad_etapa(op_id, nueva_etapa):619    db_execute("UPDATE crm_oportunidades SET etapa = ? WHERE id = ?", (nueva_etapa, op_id))620 621def get_sst_cursos():622    return db_fetchall("SELECT * FROM sst_cursos WHERE estado = 'Activo' ORDER BY nombre ASC")623 624def create_sst_curso(data):625    from datetime import datetime626    db_execute("""627        INSERT INTO sst_cursos (nombre, proyecto_id, fecha_sesion, temas_tratados, duracion, tipo, periodicidad_reinduccion, obligatorio, entidad, instructor_interno, personal_json, observaciones, fecha_registro)628        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)629    """, (630        data['nombre'], data.get('proyecto_id'), data.get('fecha_sesion'), 631        data.get('temas_tratados', ''), data.get('duracion', '4h'), 632        data.get('tipo', 'Técnico'), data.get('periodicidad_reinduccion', 12),633        data.get('obligatorio', 1), data.get('entidad', ''), 634        data.get('instructor_interno', 0), data.get('personal_json', '[]'),635        data.get('observaciones', ''), datetime.now().strftime('%Y-%m-%d %H:%M')636    ))637 638def get_sst_plan_anual(anio=2026):639    return db_fetchall("SELECT * FROM sst_plan_anual WHERE anio = ? ORDER BY fecha_programada ASC", (anio,))640 641def create_sst_plan_actividad(data):642    db_execute("""643        INSERT INTO sst_plan_anual (644            anio, nombre_actividad, descripcion, tipo_actividad, fase_phva, 645            responsable_id, fecha_programada, hora_programada, duracion_estimada, 646            riesgo_id, tipo_capacitacion, es_obligatoria, participantes_json, 647            indicador_esperado, metodo_verificacion, evidencia_requerida, estado648        )649        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)650    """, (651        data.get('anio', 2026), data.get('nombre'), data.get('descripcion'), 652        data.get('tipo'), data.get('fase'), data.get('responsable_id'), 653        data.get('fecha'), data.get('hora'), data.get('duracion'), 654        data.get('riesgo_id'), data.get('tipo_capacitacion'), 655        data.get('es_obligatoria', 1), data.get('participantes_json'), 656        data.get('indicador_esperado'), data.get('metodo_verificacion'), 657        data.get('evidencia_requerida'), data.get('estado', 'Programada')658    ))659 660def get_proyectos_by_cliente_id(cliente_id):661    return db_fetchall("SELECT * FROM proyectos WHERE cliente_id = ? ORDER BY id DESC", (cliente_id,))662 663def get_sst_peligros():664    return db_fetchall("SELECT id, peligro_descripcion, clasificacion FROM sst_matriz_peligros ORDER BY id DESC")665 666def get_crm_oportunidades_by_cliente_id(cliente_id):667    return db_fetchall("SELECT * FROM crm_oportunidades WHERE cliente_id = ? ORDER BY fecha_creacion DESC", (cliente_id,))668 669def update_actividad_proyecto(id, data, solicitante_id=None):670    """Actualiza una actividad de proyecto con un diccionario de datos dinámico."""671    fields = []672    values = []673    for key, val in data.items():674        fields.append(f"{key} = ?")675        values.append(val)676    values.append(id)677    query = f"UPDATE actividades_proyecto SET {', '.join(fields)} WHERE id = ?"678    db_execute(query, tuple(values))679 680    # Verificar si ya existe una solicitud para esta actividad681    try:682        a_id = int(id)683        if any(data.get(k) == 1 for k in ['requiere_aro', 'requiere_jsa', 'requiere_permiso']):684            # Buscamos cualquier solicitud previa (no solo pendiente) para esta actividad685            sol_existente = db_fetchone("SELECT id, estado FROM sst_solicitudes_permisos WHERE actividad_id = ?", (a_id,))686            687            act = get_actividad_by_id(a_id)688            if not act: return689 690            formatos = []691            if act.get('requiere_aro'): formatos.append("ARO")692            if act.get('requiere_jsa'): formatos.append("JSA")693            if act.get('requiere_permiso'): formatos.append("Permiso Alto Riesgo")694            detalles_sol = f"Solicitud de formatos para: {act['nombre']}. Requeridos: {', '.join(formatos)}"695 696            if sol_existente:697                # Si existe y está pendiente, podemos actualizar los detalles por si cambiaron los formatos requeridos698                if sol_existente['estado'] == 'Pendiente':699                    db_execute("UPDATE sst_solicitudes_permisos SET detalles = ? WHERE id = ?", (detalles_sol, sol_existente['id']))700            else:701                # Solo crear si no existe ninguna solicitud para esta actividad702                from datetime import datetime703                fecha_sol = datetime.now().strftime('%Y-%m-%d %H:%M')704                final_sol_id = solicitante_id or act.get('responsable_id') or 1705                706                res_pet = db_fetchone("SELECT MAX(id) as last_id FROM sst_solicitudes_permisos")707                next_pet = (res_pet['last_id'] or 0) + 1708                consecutivo_pet = f"PET-{next_pet:03d}"709 710                db_execute("""711                    INSERT INTO sst_solicitudes_permisos (proyecto_id, solicitante_id, responsable_sst_id, fecha_solicitud, estado, detalles, consecutivo, actividad_id)712                    VALUES (?, ?, ?, ?, ?, ?, ?, ?)713                """, (act['proyecto_id'], final_sol_id, act.get('responsable_sst_id') or 1, fecha_sol, 'Pendiente', detalles_sol, consecutivo_pet, a_id))714    except Exception as e:715        print(f"Error en disparador de solicitud SST: {e}")716