Edoruin commited on
Commit
351284d
·
1 Parent(s): 5980851

add decline and acceptation buttons

Browse files
Files changed (2) hide show
  1. app/main.py +157 -41
  2. app/requirements.txt +1 -0
app/main.py CHANGED
@@ -4,7 +4,48 @@ import os
4
  import base64
5
  import requests
6
  import datetime
7
- import socket # Importado para manejar errores de DNS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  def main(page: ft.Page):
10
  page.title = "MAKERSPACE DATABASE"
@@ -18,35 +59,87 @@ def main(page: ft.Page):
18
  GIT_TOKEN = os.getenv("GITLAB_TOKEN")
19
  GIT_GROUP = os.getenv("GITLAB_GROUP_ID")
20
 
21
- historial_datos = []
22
-
23
- # Crear una sesión persistente para estabilizar la conexión en el contenedor
24
- http_session = requests.Session()
25
 
26
- def enviar_telegram(mensaje):
27
- # Reemplaza con la URL que copiaste de Google
28
- GOOGLE_PROXY_URL = f"https://script.google.com/macros/s/AKfycbz7z1Jb0vsur42GmmqrL3PVXeRkN2WxSojFDIleEDoLOg6MnrmJjb_uuPcQ15CTwyzD/exec"
 
 
29
 
 
 
 
 
 
 
 
 
30
 
31
- chat_id = str(os.getenv("TELEGRAM_CHAT_ID", "")).strip()
32
- payload = {"chat_id": chat_id, "text": mensaje}
33
-
34
  try:
35
- # Hugging Face sí permite hablar con Google
36
- response = requests.post(GOOGLE_PROXY_URL, json=payload, timeout=15)
37
- if "OK" in response.text:
38
- return "OK"
39
- return "ERR_PROXY"
40
- except:
41
- return "ERR_FINAL"
42
-
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  def mostrar_factura(i):
 
 
 
 
 
 
46
  dialog = ft.AlertDialog(
47
  title=ft.Text("RECIBO DE PRÉSTAMO", weight="bold"),
48
  content=ft.Column([
49
- ft.Text(f"ESTADO: {'✅ ENVIADO' if i['status']=='OK' else '⚠️ LOCAL'}", color="green" if i['status']=='OK' else "orange", weight="bold"),
50
  ft.Divider(),
51
  ft.Text(f"ALUMNO: {i['alumno']}", size=16),
52
  ft.Text(f"ITEM: {i['item']}", weight="bold"),
@@ -54,6 +147,7 @@ def main(page: ft.Page):
54
  ft.Divider(),
55
  ft.Text(f"SALIDA: {i['hora']}"),
56
  ft.Text(f"RETORNO EST.: {i['devolucion']}"),
 
57
  ], tight=True, spacing=5),
58
  actions=[ft.TextButton("Cerrar", on_click=lambda _: page.close_dialog())]
59
  )
@@ -64,7 +158,6 @@ def main(page: ft.Page):
64
  def route_change(route):
65
  page.views.clear()
66
 
67
- # --- MENÚ PRINCIPAL ---
68
  if page.route == "/":
69
  page.views.append(
70
  ft.View("/", [
@@ -77,7 +170,6 @@ def main(page: ft.Page):
77
  ])
78
  )
79
 
80
- # --- SECCIÓN PRÉSTAMOS ---
81
  elif page.route == "/prestamos":
82
  ahora_rd = (datetime.datetime.utcnow() - datetime.timedelta(hours=4)).strftime('%H:%M')
83
  nombre = ft.TextField(label="Alumno", expand=True)
@@ -89,43 +181,72 @@ def main(page: ft.Page):
89
  lista_historial = ft.ListView(expand=True, spacing=10)
90
 
91
  def crear_card(d):
 
 
 
 
 
 
 
 
 
92
  return ft.Container(
93
  content=ft.ListTile(
94
  title=ft.Text(d['alumno'], weight="bold"),
95
  subtitle=ft.Text(f"{d['item']} (x{d['cantidad']})"),
96
- trailing=ft.Icon(ft.Icons.CHECK_CIRCLE, color="green") if d['status']=="OK" else ft.Icon(ft.Icons.REPORT_GMAILERRORRED, color="orange"),
97
  on_click=lambda _: mostrar_factura(d)
98
  ), bgcolor="#2d3238", border_radius=10
99
  )
100
 
101
- for d in reversed(historial_datos):
102
- lista_historial.controls.append(crear_card(d))
 
 
 
 
 
103
 
104
  def registrar(e):
105
  if not nombre.value or not item.value:
106
  st_txt.value = "⚠️ Completa los datos"; st_txt.color="red"; page.update(); return
107
- res = enviar_telegram(f"🛠 *PRÉSTAMO*\n👤 {nombre.value}\n📦 {item.value}\n🔢 Cant: {cant.value}\n🕒 Hora: {h_ext.value}")
108
- nuevo = {"alumno": nombre.value, "item": item.value, "cantidad": cant.value, "hora": h_ext.value, "devolucion": h_dev.value or "--:--", "status": res}
109
- historial_datos.append(nuevo)
110
- lista_historial.controls.insert(0, crear_card(nuevo))
111
- st_txt.value = "✅ Registrado" if res == "OK" else f"❌ Error: {res}"
112
- st_txt.color = "green" if res == "OK" else "orange"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  nombre.value = ""; item.value = ""; page.update()
114
 
115
  page.views.append(
116
  ft.View("/prestamos", [
117
  ft.AppBar(title=ft.Text("Préstamos"), bgcolor="#1a1c1e"),
118
  ft.Column([
119
- ft.Text("REGISTRO", size=18, weight="bold"),
120
  nombre, ft.Row([cant, h_ext, h_dev], spacing=10), item,
121
- ft.ElevatedButton("CONFIRMAR", icon=ft.Icons.SEND, on_click=registrar, bgcolor=ft.Colors.GREEN_800, color="white", height=50, width=float("inf")),
122
- st_txt, ft.Divider(), ft.Text("HISTORIAL (Click p/ detalle)", size=18, weight="bold"),
123
  lista_historial, ft.TextButton("Volver", on_click=lambda _: page.go("/"))
124
  ], scroll=ft.ScrollMode.ADAPTIVE, expand=True, spacing=10)
125
  ])
126
  )
127
 
128
- # --- SECCIÓN LISTA REPOS ---
129
  elif page.route == "/repos":
130
  repo_list = ft.ListView(expand=True, spacing=10)
131
  page.views.append(ft.View("/repos", [ft.AppBar(title=ft.Text("Proyectos")), repo_list, ft.TextButton("Volver", on_click=lambda _: page.go("/"))]))
@@ -136,7 +257,6 @@ def main(page: ft.Page):
136
  page.update()
137
  except: pass
138
 
139
- # --- VISTA DETALLE REPO (CON README) ---
140
  elif page.route.startswith("/ver/"):
141
  parts = page.route.split("/")
142
  pid, pname = parts[2], parts[3]
@@ -145,16 +265,13 @@ def main(page: ft.Page):
145
  try:
146
  gl = gitlab.Gitlab(GITLAB_URL, private_token=GIT_TOKEN)
147
  project = gl.projects.get(pid)
148
-
149
- # Intentar cargar README
150
- readme_text = "README.md no encontrado en main/master."
151
  for branch in ["main", "master"]:
152
  try:
153
  f = project.files.get(file_path='README.md', ref=branch)
154
  readme_text = base64.b64decode(f.content).decode("utf-8")
155
  break
156
  except: continue
157
-
158
  c_area.controls.append(ft.Markdown(readme_text, selectable=True, extension_set=ft.MarkdownExtensionSet.GITHUB_WEB))
159
  c_area.controls.append(ft.Divider())
160
  c_area.controls.append(ft.Row([
@@ -163,7 +280,6 @@ def main(page: ft.Page):
163
  ], alignment=ft.MainAxisAlignment.CENTER))
164
  page.update()
165
  except: pass
166
-
167
  page.update()
168
 
169
  page.on_route_change = route_change
 
4
  import base64
5
  import requests
6
  import datetime
7
+ import socket
8
+ import json
9
+ import threading
10
+ import uuid
11
+ import telebot
12
+ from telebot import types
13
+
14
+ # --- CLASE PARA PERSISTENCIA ---
15
+ class LoanManager:
16
+ def __init__(self, filename="prestamos.json"):
17
+ self.filename = filename
18
+ self.loans = self._load()
19
+
20
+ def _load(self):
21
+ if os.path.exists(self.filename):
22
+ try:
23
+ with open(self.filename, "r") as f:
24
+ return json.load(f)
25
+ except:
26
+ return []
27
+ return []
28
+
29
+ def save(self):
30
+ with open(self.filename, "w") as f:
31
+ json.dump(self.loans, f, indent=4)
32
+
33
+ def add_loan(self, loan):
34
+ self.loans.append(loan)
35
+ self.save()
36
+
37
+ def update_status(self, loan_id, status):
38
+ for loan in self.loans:
39
+ if loan["id"] == loan_id:
40
+ loan["status_loan"] = status
41
+ self.save()
42
+ return True
43
+ return False
44
+
45
+ def get_all(self):
46
+ return self.loans
47
+
48
+ loan_mgr = LoanManager()
49
 
50
  def main(page: ft.Page):
51
  page.title = "MAKERSPACE DATABASE"
 
59
  GIT_TOKEN = os.getenv("GITLAB_TOKEN")
60
  GIT_GROUP = os.getenv("GITLAB_GROUP_ID")
61
 
62
+ # Inicializar bot
63
+ bot = telebot.TeleBot(TG_TOKEN) if TG_TOKEN else None
 
 
64
 
65
+ def mostrar_notificacion(texto, color="blue"):
66
+ snack = ft.SnackBar(ft.Text(texto), bgcolor=color)
67
+ page.overlay.append(snack)
68
+ snack.open = True
69
+ page.update()
70
 
71
+ def enviar_telegram_con_botones(loan_id, mensaje):
72
+ if not bot or not TG_CHAT_ID:
73
+ return "ERR_TOKEN"
74
+
75
+ markup = types.InlineKeyboardMarkup()
76
+ btn_aceptar = types.InlineKeyboardButton("✅ Aceptar", callback_data=f"accept_{loan_id}")
77
+ btn_declinar = types.InlineKeyboardButton("❌ Declinar", callback_data=f"decline_{loan_id}")
78
+ markup.add(btn_aceptar, btn_declinar)
79
 
 
 
 
80
  try:
81
+ bot.send_message(TG_CHAT_ID, mensaje, reply_markup=markup, parse_mode="Markdown")
82
+ return "OK"
83
+ except Exception as e:
84
+ print(f"Error Telegram: {e}")
85
+ return "ERR_TG"
 
 
 
86
 
87
+ # --- BOT CALLBACK HANDLER ---
88
+ @bot.callback_query_handler(func=lambda call: True)
89
+ def handle_query(call):
90
+ if call.data.startswith("accept_"):
91
+ loan_id = call.data.replace("accept_", "")
92
+ if loan_mgr.update_status(loan_id, "ACCEPTED"):
93
+ bot.answer_callback_query(call.id, "Préstamo Aceptado")
94
+ bot.edit_message_text(f"✅ *ACEPTADO*\n{call.message.text}", TG_CHAT_ID, call.message.message_id, parse_mode="Markdown")
95
+ mostrar_notificacion(f"Préstamo {loan_id[:8]} ACEPTADO", ft.Colors.GREEN)
96
+ elif call.data.startswith("decline_"):
97
+ loan_id = call.data.replace("decline_", "")
98
+ if loan_mgr.update_status(loan_id, "DECLINED"):
99
+ bot.answer_callback_query(call.id, "Préstamo Declinado")
100
+ bot.edit_message_text(f"❌ *DECLINADO*\n{call.message.text}", TG_CHAT_ID, call.message.message_id, parse_mode="Markdown")
101
+ mostrar_notificacion(f"Préstamo {loan_id[:8]} DECLINADO", ft.Colors.RED)
102
+ page.update()
103
+
104
+ # --- BOT COMMAND HANDLER ---
105
+ @bot.message_handler(commands=['aceptar', 'declinar', 'status'])
106
+ def handle_text_commands(message):
107
+ text = message.text.split()
108
+ if len(text) < 2:
109
+ bot.reply_to(message, "Uso: /aceptar <id> o /declinar <id>")
110
+ return
111
 
112
+ cmd = text[0][1:] # 'aceptar' o 'declinar'
113
+ loan_id = text[1]
114
+
115
+ status = "ACCEPTED" if cmd == "aceptar" else "DECLINED"
116
+ if loan_mgr.update_status(loan_id, status):
117
+ emoji = "✅" if status == "ACCEPTED" else "❌"
118
+ bot.reply_to(message, f"{emoji} Préstamo {loan_id} actualizado a {status}")
119
+ mostrar_notificacion(f"Préstamo {loan_id[:8]} {status}", ft.Colors.GREEN if status == "ACCEPTED" else ft.Colors.RED)
120
+ else:
121
+ bot.reply_to(message, "ID de pr��stamo no encontrado.")
122
+ page.update()
123
+
124
+ def start_bot():
125
+ if bot:
126
+ bot.infinity_polling()
127
+
128
+ # Iniciar polling en hilo separado
129
+ if TG_TOKEN:
130
+ threading.Thread(target=start_bot, daemon=True).start()
131
+
132
  def mostrar_factura(i):
133
+ status_color = {
134
+ "PENDING": ft.Colors.ORANGE,
135
+ "ACCEPTED": ft.Colors.GREEN,
136
+ "DECLINED": ft.Colors.RED
137
+ }.get(i.get('status_loan', 'PENDING'), ft.Colors.GREY)
138
+
139
  dialog = ft.AlertDialog(
140
  title=ft.Text("RECIBO DE PRÉSTAMO", weight="bold"),
141
  content=ft.Column([
142
+ ft.Text(f"ESTADO: {i.get('status_loan', 'PENDING')}", color=status_color, weight="bold"),
143
  ft.Divider(),
144
  ft.Text(f"ALUMNO: {i['alumno']}", size=16),
145
  ft.Text(f"ITEM: {i['item']}", weight="bold"),
 
147
  ft.Divider(),
148
  ft.Text(f"SALIDA: {i['hora']}"),
149
  ft.Text(f"RETORNO EST.: {i['devolucion']}"),
150
+ ft.Text(f"ID: {i['id']}", size=10, color="grey"),
151
  ], tight=True, spacing=5),
152
  actions=[ft.TextButton("Cerrar", on_click=lambda _: page.close_dialog())]
153
  )
 
158
  def route_change(route):
159
  page.views.clear()
160
 
 
161
  if page.route == "/":
162
  page.views.append(
163
  ft.View("/", [
 
170
  ])
171
  )
172
 
 
173
  elif page.route == "/prestamos":
174
  ahora_rd = (datetime.datetime.utcnow() - datetime.timedelta(hours=4)).strftime('%H:%M')
175
  nombre = ft.TextField(label="Alumno", expand=True)
 
181
  lista_historial = ft.ListView(expand=True, spacing=10)
182
 
183
  def crear_card(d):
184
+ icon = ft.Icons.HOURGLASS_EMPTY
185
+ color = ft.Colors.ORANGE
186
+ if d.get('status_loan') == "ACCEPTED":
187
+ icon = ft.Icons.CHECK_CIRCLE
188
+ color = ft.Colors.GREEN
189
+ elif d.get('status_loan') == "DECLINED":
190
+ icon = ft.Icons.CANCEL
191
+ color = ft.Colors.RED
192
+
193
  return ft.Container(
194
  content=ft.ListTile(
195
  title=ft.Text(d['alumno'], weight="bold"),
196
  subtitle=ft.Text(f"{d['item']} (x{d['cantidad']})"),
197
+ trailing=ft.Icon(icon, color=color),
198
  on_click=lambda _: mostrar_factura(d)
199
  ), bgcolor="#2d3238", border_radius=10
200
  )
201
 
202
+ def refactor_list():
203
+ lista_historial.controls.clear()
204
+ for d in reversed(loan_mgr.get_all()):
205
+ lista_historial.controls.append(crear_card(d))
206
+ page.update()
207
+
208
+ refactor_list()
209
 
210
  def registrar(e):
211
  if not nombre.value or not item.value:
212
  st_txt.value = "⚠️ Completa los datos"; st_txt.color="red"; page.update(); return
213
+
214
+ loan_id = str(uuid.uuid4())
215
+ mensaje_tg = f"🛠 *SOLICITUD DE PRÉSTAMO*\n👤 {nombre.value}\n📦 {item.value}\n🔢 Cant: {cant.value}\n🕒 Hora: {h_ext.value}"
216
+
217
+ res_tg = enviar_telegram_con_botones(loan_id, mensaje_tg)
218
+
219
+ nuevo = {
220
+ "id": loan_id,
221
+ "alumno": nombre.value,
222
+ "item": item.value,
223
+ "cantidad": cant.value,
224
+ "hora": h_ext.value,
225
+ "devolucion": h_dev.value or "--:--",
226
+ "status_loan": "PENDING",
227
+ "status_tg": res_tg
228
+ }
229
+
230
+ loan_mgr.add_loan(nuevo)
231
+ refactor_list()
232
+
233
+ st_txt.value = "✅ Solicitud Enviada" if res_tg == "OK" else f"⚠️ Error Telegram: {res_tg}"
234
+ st_txt.color = "green" if res_tg == "OK" else "orange"
235
  nombre.value = ""; item.value = ""; page.update()
236
 
237
  page.views.append(
238
  ft.View("/prestamos", [
239
  ft.AppBar(title=ft.Text("Préstamos"), bgcolor="#1a1c1e"),
240
  ft.Column([
241
+ ft.Text("SOLICITAR HERRAMIENTA", size=18, weight="bold"),
242
  nombre, ft.Row([cant, h_ext, h_dev], spacing=10), item,
243
+ ft.ElevatedButton("ENVIAR SOLICITUD", icon=ft.Icons.SEND, on_click=registrar, bgcolor=ft.Colors.GREEN_800, color="white", height=50, width=float("inf")),
244
+ st_txt, ft.Divider(), ft.Text("HISTORIAL DE SOLICITUDES", size=18, weight="bold"),
245
  lista_historial, ft.TextButton("Volver", on_click=lambda _: page.go("/"))
246
  ], scroll=ft.ScrollMode.ADAPTIVE, expand=True, spacing=10)
247
  ])
248
  )
249
 
 
250
  elif page.route == "/repos":
251
  repo_list = ft.ListView(expand=True, spacing=10)
252
  page.views.append(ft.View("/repos", [ft.AppBar(title=ft.Text("Proyectos")), repo_list, ft.TextButton("Volver", on_click=lambda _: page.go("/"))]))
 
257
  page.update()
258
  except: pass
259
 
 
260
  elif page.route.startswith("/ver/"):
261
  parts = page.route.split("/")
262
  pid, pname = parts[2], parts[3]
 
265
  try:
266
  gl = gitlab.Gitlab(GITLAB_URL, private_token=GIT_TOKEN)
267
  project = gl.projects.get(pid)
268
+ readme_text = "README.md no encontrado."
 
 
269
  for branch in ["main", "master"]:
270
  try:
271
  f = project.files.get(file_path='README.md', ref=branch)
272
  readme_text = base64.b64decode(f.content).decode("utf-8")
273
  break
274
  except: continue
 
275
  c_area.controls.append(ft.Markdown(readme_text, selectable=True, extension_set=ft.MarkdownExtensionSet.GITHUB_WEB))
276
  c_area.controls.append(ft.Divider())
277
  c_area.controls.append(ft.Row([
 
280
  ], alignment=ft.MainAxisAlignment.CENTER))
281
  page.update()
282
  except: pass
 
283
  page.update()
284
 
285
  page.on_route_change = route_change
app/requirements.txt CHANGED
@@ -1,3 +1,4 @@
1
  flet
2
  python-gitlab
3
  requests
 
 
1
  flet
2
  python-gitlab
3
  requests
4
+ pyTelegramBotAPI