fmatituy/selectospromanager
0
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 