fmatituy/selectospromanager
0
1from nicegui import ui, app, run
2from database.queries import get_all_roles, create_user
3from database.db import hash_password
4from services.auth import authenticate, is_authenticated
5from modules.layout import init_pwa_headers
6from core.events import event_bus
7from core.logger import logger
8
9def register_auth_pages():
10 @ui.page('/login')
11 def login_page(return_to: str = '/'):
12 init_pwa_headers()
13 # Bypass si ya está autenticado realmente
14 if is_authenticated():
15 ui.navigate.to(return_to)
16 return
17
18 ui.colors(primary='#1e40af', secondary='#475569')
19 main_container = ui.column().classes('w-full h-screen items-center justify-center p-0 bg-slate-900 relative overflow-hidden')
20
21 with main_container:
22 # Capa de Imagen Full Bleed
23 with ui.element('div').classes('absolute inset-0 w-full h-full p-0 overflow-hidden'):
24 # Imagen de fondo sin márgenes (Ultra HD)
25 ui.image('/static/app/login_bg.png').classes('absolute inset-0 w-full h-full object-cover')
26
27 # Degradado oscuro sutil para legibilidad del logo
28 with ui.element('div').classes('absolute inset-0 bg-gradient-to-b from-black/40 via-transparent to-black/20'):
29 pass
30
31 pass
32
33 pass
34
35 with ui.card().classes('w-full max-w-[360px] p-8 shadow-2xl rounded-[2.5rem] z-10 bg-white border border-slate-200 mx-4 transition-all hover:shadow-blue-900/10'):
36 with ui.column().classes('items-center w-full mb-6'):
37 with ui.element('div').classes('w-16 h-16 bg-blue-50 rounded-2xl flex items-center justify-center mb-6 shadow-inner border border-blue-100'):
38 ui.icon('settings', size='40px', color='primary').classes('animate-[spin_6s_linear_infinite]')
39
40 with ui.row().classes('items-center gap-1 sm:gap-1.5 leading-none'):
41 ui.label('PRO').classes('text-2xl sm:text-3xl font-black tracking-tighter text-blue-600')
42 with ui.element('div').classes('w-6 h-6 sm:w-8 sm:h-8 rounded-full flex items-center justify-center bg-blue-600 shadow-lg'):
43 ui.icon('settings', size='14px', color='white') # Corregido: nicegui no soporta clases responsive en size prop directamente, se maneja estilo
44 ui.label('MANAGER').classes('text-2xl sm:text-3xl font-black tracking-tighter text-slate-800')
45 ui.label('ERP SISTEMA INTEGRAL DE GESTIÓN DE PROYECTOS').classes('text-[8px] sm:text-[9px] font-bold tracking-[0.2em] text-slate-400 uppercase mt-1 text-center px-2')
46
47 username = ui.input('Usuario').props('outlined rounded-3xl autocomplete="off"').classes('w-full text-sm mb-4 bg-slate-50/50')
48 password = ui.input('Contraseña', password=True, password_toggle_button=True).props('outlined rounded-3xl autocomplete="new-password"').classes('w-full text-sm mb-8 bg-slate-50/50')
49
50 login_btn = ui.button('INICIAR SESIÓN').props('unelevated color=primary size=lg rounded-3xl').classes('w-full py-4 font-black shadow-xl shadow-blue-200 text-xs tracking-widest mb-4')
51
52 async def try_login(e=None):
53 u, p = username.value, password.value
54 login_btn.props('loading')
55 try:
56 user_data = await run.io_bound(authenticate, u, p)
57 if user_data:
58 app.storage.user.update(user_data)
59 app.storage.user['authenticated'] = True
60 app.storage.browser['auto_user'] = u
61 app.storage.browser['auto_pass'] = p
62
63 # Disparar evento de login para otros módulos (decopupling)
64 await event_bus.emit("auth:login", {"username": u, "rol": user_data.get("rol")})
65 logger.info(f"Usuario {u} autenticado exitosamente.")
66
67 ui.navigate.to(return_to)
68 else:
69 login_btn.props(remove='loading')
70 ui.notify('Credenciales incorrectas', type='negative', position='top', icon='error')
71 except Exception as ex:
72 login_btn.props(remove='loading')
73 if 'already been built' in str(ex):
74 ui.run_javascript(f'window.location.href = "{return_to}"')
75 else:
76 ui.notify(f'Error de acceso: {str(ex)}', type='negative')
77
78 login_btn.on_click(try_login)
79 username.on('keydown.enter', try_login)
80 password.on('keydown.enter', try_login)
81 ui.label('Acceso restringido a personal autorizado.').classes('text-[10px] text-center w-full text-slate-400 font-bold mb-8')
82
83 stored_user = app.storage.browser.get('auto_user')
84 stored_pass = app.storage.browser.get('auto_pass')
85 if stored_user and stored_pass:
86 async def background_auto_login():
87 try:
88 user_data = await run.io_bound(authenticate, stored_user, stored_pass)
89 if user_data:
90 app.storage.user.update(user_data)
91 app.storage.user['authenticated'] = True
92 ui.navigate.to(return_to)
93 except: pass
94 ui.timer(0.5, background_auto_login, once=True)
95
96 @ui.page('/registro')
97 def registro_page():
98 ui.navigate.to('/login')
99 ui.notify('El registro manual está deshabilitado. Contacte al administrador.', type='warning')
100 return
101
102 @ui.page('/descargar/apk')
103 def descargar_apk_page():
104 with ui.column().classes('w-full h-screen items-center justify-center bg-slate-900 p-8 text-center text-white'):
105 ui.icon('android', size='80px', color='emerald-500').classes('mb-6')
106 ui.label('Selectos ProManager para Android').classes('text-3xl font-black mb-2')
107 ui.label('Versión 1.0.3 stable').classes('text-xs text-emerald-400 font-bold uppercase tracking-widest mb-6 p-2 bg-emerald-950 rounded-lg')
108
109 with ui.card().classes('w-full max-w-sm p-8 rounded-[2.5rem] bg-white/5 border border-white/10 text-left'):
110 ui.label('INSTRUCCIONES DE INSTALACIÓN').classes('text-[10px] font-black text-slate-400 mb-6 tracking-widest')
111 steps = [
112 '1. Descarga el archivo APK pulsando el botón abajo.',
113 '2. Abre el archivo en tu celular.',
114 '3. Si aparece "Bloqueado por Play Protect", selecciona "Instalar de todas formas".',
115 '4. ¡Inicia sesión y disfruta de la potencia de Selectos!'
116 ]
117 for s in steps:
118 ui.label(s).classes('text-sm mb-4 leading-relaxed font-medium')
119 ui.button('DESCARGAR AHORA (APK)', icon='download', on_click=lambda: ui.download('/static/apk/Selectos_ProManager.apk')).props('unelevated color=emerald-600 rounded-2xl h-14').classes('w-full font-black mt-6 shadow-2xl shadow-emerald-900/40')
120 ui.button('Volver al ERP', icon='arrow_back', on_click=lambda: ui.navigate.to('/')).props('flat color=white').classes('mt-8 opacity-50')
121 