|
|
""" |
|
|
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 |
|
|
|
|
|
|
|
|
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' |
|
|
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 |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
code = f"{random.randint(100000, 999999)}" |
|
|
st.session_state.sms_code = code |
|
|
|
|
|
log(f"[SMS] Código generado para {phone_number}: {code}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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}**") |
|
|
|
|
|
|
|
|
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.** |
|
|
""") |
|
|
|
|
|
|
|
|
with st.expander("📋 Verificació per WhatsApp/SMS", expanded=True): |
|
|
|
|
|
st.markdown(get_terms_and_conditions()) |
|
|
|
|
|
st.markdown("---") |
|
|
st.markdown("#### 📱 Dades de verificació") |
|
|
|
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
accept_terms = st.checkbox( |
|
|
"✅ Accepto les condicions d'ús i la política de privadesa", |
|
|
key="mobile_accept_terms" |
|
|
) |
|
|
|
|
|
|
|
|
if st.button( |
|
|
"📤 Enviar codi de verificació", |
|
|
type="primary", |
|
|
disabled=not (phone_number and accept_terms) |
|
|
): |
|
|
full_phone = f"{country_code}{phone_number}" |
|
|
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}") |
|
|
st.rerun() |
|
|
else: |
|
|
st.error("❌ Error enviant el codi. Torna-ho a intentar.") |
|
|
|
|
|
|
|
|
if st.session_state.sms_step == 'verify': |
|
|
st.markdown("#### 🔓 Verificació del codi") |
|
|
|
|
|
|
|
|
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("<br>", 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 |
|
|
|
|
|
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}") |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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 |
|
|
""" |
|
|
|
|
|
base_permissions = { |
|
|
"verd": { |
|
|
"analizar": True, |
|
|
"procesar_videos": True, |
|
|
"valorar": True, |
|
|
"validar": True, |
|
|
"estadisticas": True, |
|
|
"requires_sms": False |
|
|
}, |
|
|
"groc": { |
|
|
"analizar": True, |
|
|
"procesar_videos": True, |
|
|
"valorar": True, |
|
|
"validar": False, |
|
|
"estadisticas": True, |
|
|
"requires_sms": True |
|
|
}, |
|
|
"blau": { |
|
|
"analizar": True, |
|
|
"procesar_videos": False, |
|
|
"valorar": True, |
|
|
"validar": True, |
|
|
"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() |
|
|
|
|
|
|
|
|
if permissions["requires_sms"]: |
|
|
if sms_verified is False: |
|
|
|
|
|
permissions["procesar_videos"] = False |
|
|
permissions["validar"] = False |
|
|
elif sms_verified is None: |
|
|
|
|
|
permissions["procesar_videos"] = False |
|
|
permissions["validar"] = False |
|
|
|
|
|
|
|
|
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") |
|
|
|