""" Módulo de verificación por móvil/SMS para usuarios que requieren validación adicional. Este módulo gestiona: - Mostrar términos y condiciones de uso - Solicitar número de teléfono móvil - Enviar código de verificación por SMS (simulado en demo) - Verificar código introducido por el usuario """ import sys import random import streamlit as st from datetime import datetime from typing import Optional, Tuple from compliance_client import compliance_client def log(msg: str): """Helper per logging amb timestamp""" timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") sys.stderr.write(f"[{timestamp}] {msg}\n") sys.stderr.flush() def initialize_sms_state(): """Inicializa el estado de verificación SMS si no existe""" if 'sms_step' not in st.session_state: st.session_state.sms_step = 'phone' # 'phone' o 'verify' if 'sms_code' not in st.session_state: st.session_state.sms_code = None if 'sms_phone' not in st.session_state: st.session_state.sms_phone = None if 'sms_verified' not in st.session_state: st.session_state.sms_verified = None # None=pendiente, True=verificado, False=omitido def send_sms_code(phone_number: str) -> bool: """ Simula el envío de código SMS (en producción usaría un servicio real como Twilio). Args: phone_number: Número de teléfono completo con código de país Returns: True si el envío fue exitoso, False en caso de error """ try: # Generar código aleatorio de 6 dígitos code = f"{random.randint(100000, 999999)}" st.session_state.sms_code = code log(f"[SMS] Código generado para {phone_number}: {code}") # En producción aquí iría la llamada a un servicio real de SMS # Por ejemplo, usando Twilio: # client = TwilioClient(account_sid, auth_token) # message = client.messages.create( # body=f"El teu codi de verificació Veureu és: {code}", # from_=twilio_phone, # to=phone_number # ) return True except Exception as e: log(f"[SMS] Error enviando código: {e}") return False def get_terms_and_conditions() -> str: """Retorna el texto completo de términos y condiciones""" return """ ### **Condicions d'ús del sistema Veureu** En iniciar sessió i pujar un vídeo al sistema Veureu, l'usuari declara i accepta el següent: **📋 Declaracions:** - Declara ser titular dels drets d'ús i difusió del vídeo, o disposar de l'autorització expressa dels titulars. - Declara que totes les persones identificables en el vídeo han atorgat el seu consentiment informat per a la seva utilització amb finalitats d'accessibilitat i audiodescripció. - Declara que el vídeo no conté: - Contingut violent, sexual o d'odi, - Informació confidencial o de caràcter sensible, - Imatges de menors sense consentiment parental verificable. **✅ Acceptacions:** - Accepta que es processi el vídeo exclusivament amb finalitats de generació i validació d'audiodescripcions, conforme a la normativa UNE-153010:2020 i al RGPD. - Accepta que el sistema pugui enviar les dades necessàries a proveïdors tecnològics externs (p. ex., models d'IA) que actuen com a encarregats de tractament, sota clàusules contractuals tipus i sense reutilització de dades. - Accepta que les accions realitzades (pujada, acceptació, validació, revocació) siguin registrades en un sistema immutable (AWS QLDB) mitjançant identificadors no personals. - Pot exercir en qualsevol moment el seu dret a revocar el consentiment mitjançant el botó "Revocar permisos", el que eliminarà el material audiovisual i deixarà constància de la revocació en el registre. - Accepta que, fins a la validació interna del material per part de l'equip Veureu, el vídeo romandrà en estat "pendent de validació" i no serà utilitzat públicament. """ def render_mobile_verification_screen(username: str, role: str) -> Optional[bool]: """ Renderiza la pantalla de verificación por móvil post-login. Args: username: Nombre de usuario autenticado role: Rol del usuario Returns: True si el usuario completó la verificación False si omitió la verificación (solo modo análisis) None si aún está en proceso """ st.title("Veureu — Audiodescripció") st.markdown(f"Benvingut/da, **{username}**") # Mensaje explicativo según el rol if role in ["groc", "blau"]: st.info(""" ### 📱 Verificació per WhatsApp/SMS requerida Per accedir a les funcions completes del sistema (pujar vídeos, validar contingut), cal verificar el seu número de mòbil i acceptar les condicions d'ús. **Si únicament vol analitzar i modificar les audiodescripcions existents, pot ometre aquest pas.** """) # Ventana desplegable con términos y verificación SMS with st.expander("📋 Verificació per WhatsApp/SMS", expanded=True): # Términos y condiciones st.markdown(get_terms_and_conditions()) st.markdown("---") st.markdown("#### 📱 Dades de verificació") # Selector de país y número de teléfono col_country, col_phone = st.columns([1, 3]) with col_country: country_code = st.selectbox("País", [ ("🇪🇸 +34", "+34"), ("🇫🇷 +33", "+33"), ("🇬🇧 +44", "+44"), ("🇩🇪 +49", "+49"), ("🇮🇹 +39", "+39") ], format_func=lambda x: x[0])[1] with col_phone: phone_number = st.text_input( "Número de telèfon" + (" (opcional)" if role == "groc" else ""), placeholder="600 123 000", max_chars=15 ) # Checkbox para aceptar términos accept_terms = st.checkbox( "✅ Accepto les condicions d'ús i la política de privadesa", key="mobile_accept_terms" ) # Botón para enviar código (SMS demo) i, en paral·lel, registrar sessió per WhatsApp al backend if st.button( "📤 Enviar codi de verificació", type="primary", disabled=not (phone_number and accept_terms) ): full_phone = f"{country_code}{phone_number}" # 1) Flux SMS demo local (no canvia) if send_sms_code(full_phone): st.session_state.sms_step = 'verify' st.session_state.sms_phone = full_phone st.success(f"✅ Codi enviat a {full_phone}") else: st.error("❌ Error enviant el codi. Torna-ho a intentar.") # 2) Crear sessió de verificació per WhatsApp al backend de compliance try: user_obj = st.session_state.get("user") or {} user_id = str(user_obj.get("id")) if isinstance(user_obj, dict) and "id" in user_obj else None email = None # es pot afegir més endavant si hi ha email disponible resp = compliance_client.create_phone_verification_session( page="demo/mobile_verification", user_id=user_id, email=email, action="accept_terms_mobile_verification", ) if resp and "session_code" in resp: session_code = resp["session_code"] expires_at = resp.get("expires_at") st.info( "📱 **Verificació per WhatsApp (registre de firma)**\n\n" "Envia un WhatsApp des del teu mòbil al **número oficial del projecte** amb el text:\n\n" f"`ACEPTO {session_code}`\n\n" "Tens fins a **5 minuts** per enviar-lo." ) else: log("[COMPLIANCE] No s'ha pogut crear la sessió de verificació per telèfon") except Exception as e: log(f"[COMPLIANCE] Error cridant create_phone_verification_session: {e}") # Si se envió el SMS, mostrar campo para verificar código if st.session_state.sms_step == 'verify': st.markdown("#### 🔓 Verificació del codi") # Mostrar código en modo demo if st.session_state.sms_code: st.info(f"💡 **Mode demo**: El codi és '{st.session_state.sms_code}'") col_code, col_resend = st.columns([2, 1]) with col_code: verification_code = st.text_input( "Codi de 6 dígits", max_chars=6, placeholder="000000", key="mobile_verification_code" ) with col_resend: st.markdown("
", unsafe_allow_html=True) if st.button("🔄 Reenviar", key="mobile_resend"): if send_sms_code(st.session_state.sms_phone): st.success("✅ Nou codi enviat") else: st.error("❌ Error enviant el codi") if st.button("🔓 Verificar i continuar", type="primary"): if verification_code == st.session_state.sms_code: st.session_state.sms_verified = True st.session_state.sms_phone_verified = st.session_state.sms_phone # Resetear estado SMS st.session_state.sms_step = 'phone' st.session_state.sms_code = None st.session_state.sms_phone = None st.success("✅ Verificació completada! Ara pot accedir a totes les funcions.") log(f"[SMS] Usuario {username} verificado correctamente") st.rerun() return True else: st.error(f"❌ Codi incorrecte. El codi correcte és: {st.session_state.sms_code}") # Botones de acción principales st.markdown("---") col_continue, col_skip = st.columns([1, 1]) with col_continue: if st.session_state.sms_verified is True: if st.button("🚪 Accedir amb permisos complets", type="primary"): st.success("✅ Accedint a l'aplicació amb permisos complets...") st.rerun() return True with col_skip: # Solo mostrar botón de omitir para roles que no requieren verificación obligatoria if role in ["groc", "blau"]: if st.button("⏭️ Continuar sense verificació (només anàlisi)"): st.session_state.sms_verified = False st.info("✅ Sessió iniciada. Pot accedir a les funcions d'anàlisi d'audiodescripcions.") log(f"[SMS] Usuario {username} omitió verificación SMS") st.rerun() return False return None def get_user_permissions(role: str, sms_verified: Optional[bool]) -> dict: """ Retorna los permisos del usuario según su rol y estado de verificación SMS. Args: role: Rol del usuario (verd, groc, blau, taronja, vermell) sms_verified: Estado de verificación SMS (True, False, None) Returns: Diccionario con permisos booleanos """ # Permisos base por rol (sin verificación SMS) base_permissions = { "verd": { "analizar": True, "procesar_videos": True, "valorar": True, "validar": True, "estadisticas": True, "requires_sms": False }, "groc": { "analizar": True, "procesar_videos": True, # Requiere SMS si no está verificado "valorar": True, "validar": False, "estadisticas": True, "requires_sms": True }, "blau": { "analizar": True, "procesar_videos": False, # No puede subir videos nunca "valorar": True, "validar": True, # Requiere SMS si no está verificado "estadisticas": True, "requires_sms": True }, "taronja": { "analizar": True, "procesar_videos": False, "valorar": True, "validar": False, "estadisticas": True, "requires_sms": False }, "vermell": { "analizar": True, "procesar_videos": False, "valorar": False, "validar": False, "estadisticas": True, "requires_sms": False } } permissions = base_permissions.get(role, base_permissions["vermell"]).copy() # Ajustar permisos según verificación SMS if permissions["requires_sms"]: if sms_verified is False: # Omitió verificación # Solo análisis permitido permissions["procesar_videos"] = False permissions["validar"] = False elif sms_verified is None: # Pendiente de verificación # Pendiente: no dar acceso a funciones que requieren SMS permissions["procesar_videos"] = False permissions["validar"] = False # Si sms_verified is True, mantener permisos originales return permissions def show_verification_status_in_sidebar(): """Muestra el estado de verificación SMS en la barra lateral""" if st.session_state.get('sms_verified') is True: st.success("📱 Verificació SMS activada") elif st.session_state.get('sms_verified') is False: st.warning("⚠️ Només mode anàlisi") else: st.info("⏳ Verificació pendent")