from fastapi import FastAPI, HTTPException, Depends, Header
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from typing import List, Optional
import database
import blockchain
import hashlib
from datetime import datetime, timedelta
import asyncio
import threading
import time
import secrets
import jwt
import os

# --- JWT CONFIGURATION ---
JWT_SECRET = os.environ.get("JWT_SECRET", secrets.token_hex(32))
JWT_ALGORITHM = "HS256"
JWT_EXPIRATION_HOURS = 72  # Token valid for 3 days

def create_token(alias: str) -> str:
    """Create a JWT token for a user"""
    payload = {
        "alias": alias,
        "exp": datetime.utcnow() + timedelta(hours=JWT_EXPIRATION_HOURS),
        "iat": datetime.utcnow()
    }
    return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)

def verify_token(token: str) -> dict:
    """Verify and decode a JWT token"""
    try:
        payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
        return payload
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token expirado. Inicia sesión de nuevo.")
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="Token inválido.")

async def get_current_user(authorization: Optional[str] = Header(None)) -> Optional[str]:
    """Extract current user alias from Authorization header. Returns None if no token."""
    if not authorization:
        return None
    try:
        scheme, _, token = authorization.partition(" ")
        if scheme.lower() != "bearer" or not token:
            return None
        payload = verify_token(token)
        return payload.get("alias")
    except HTTPException:
        return None

async def require_auth(authorization: Optional[str] = Header(None)) -> str:
    """Require valid JWT token. Raises 401 if missing/invalid."""
    if not authorization:
        raise HTTPException(status_code=401, detail="Se requiere autenticación. Inicia sesión.")
    scheme, _, token = authorization.partition(" ")
    if scheme.lower() != "bearer" or not token:
        raise HTTPException(status_code=401, detail="Formato de token inválido. Use: Bearer <token>")
    payload = verify_token(token)
    alias = payload.get("alias")
    if not alias:
        raise HTTPException(status_code=401, detail="Token sin identidad.")
    return alias

NAV_WORDLIST = [
    "agua", "aire", "arbol", "bosque", "cielo", "colibri", "cristal", "cueva", "dios", "eco",
    "fuego", "hoja", "jaguar", "lago", "luna", "lluvia", "madre", "maiz", "mar", "monte",
    "mundo", "nube", "oro", "piedra", "pluma", "raiz", "rio", "roca", "selva", "semilla",
    "serpiente", "sol", "tierra", "trueno", "viento", "vida", "verde", "zapallo", "zorro",
    "condor", "puma", "venado", "flor", "fruto", "rama", "tronco", "hoja", "pajaro", "pez",
    "arena", "barro", "caña", "cedro", "coco", "ceiba", "frijol", "yuca", "tabaco", "coca"
]

def generate_seed():
    """Genera una frase semilla de 12 palabras"""
    return " ".join(secrets.choice(NAV_WORDLIST) for _ in range(12))

def hash_password(semilla: str) -> str:
    """Hashea la semilla con PBKDF2 y sal estática (por simplicidad en este prototipo, idealmente salt por user)"""
    salt = b'tairuma_secure_salt' # En prod usar os.urandom(32) y guardar en DB
    return hashlib.pbkdf2_hmac('sha256', semilla.encode('utf-8'), salt, 100000).hex()

def verify_password(semilla: str, hashed: str) -> bool:
    return hash_password(semilla) == hashed

app = FastAPI(title="Numa API")

# Configurar CORS para permitir peticiones desde cualquier origen (para la red pública)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"], # Permitir todos los orígenes para la red P2P pública
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

CURRENT_VERSION = "3.1.2"

# --- TOKEN VALUE SYSTEM ---
NUMA_EUR_VALUE = 1.0  # 1 Numa = 1€ (reference value)

@app.get("/api/version")
async def get_version():
    return {"version": CURRENT_VERSION}

@app.get("/api/valor-numa")
async def get_valor_numa():
    """Returns the reference value of 1 Numa as a pricing guide"""
    return {
        "valor_eur": NUMA_EUR_VALUE,
        "descripcion": f"1 Numa ≈ {NUMA_EUR_VALUE}€ (guía de precios)",
        "nota": "El Numa no es dinero, es energía de intercambio. Esta referencia te ayuda a fijar precios justos."
    }

import requests
import json
import threading
import time
import os

# --- P2P GOSSIP DISCOVERY SYSTEM ---
PEERS = {}  # {url: {last_seen, latency_ms, version, failures, alias}}
_port = os.environ.get("PORT", "9500")
MY_NODE_URL = os.environ.get("TAIRUMA_PUBLIC_URL", f"http://localhost:{_port}")
print(f"[P2P] Mi URL pública: {MY_NODE_URL}")
PEER_LOCK = threading.Lock()
MAX_PEER_FAILURES = 5
HEARTBEAT_INTERVAL = 30   # seconds
ANNOUNCE_INTERVAL = 60    # seconds
PRUNE_INTERVAL = 120      # seconds

def load_seed_nodes():
    """Load bootstrap peers from seed_nodes.json and DB"""
    loaded = 0
    # 1. From seed_nodes.json (project root)
    for path in ["seed_nodes.json", "web/nodes.json"]:
        try:
            with open(path, "r") as f:
                seeds = json.load(f)
                for s in seeds:
                    s = s.rstrip("/")
                    if s and s != MY_NODE_URL.rstrip("/"):
                        with PEER_LOCK:
                            if s not in PEERS:
                                PEERS[s] = {"last_seen": None, "latency_ms": 0, "version": "?", "failures": 0, "alias": ""}
                                loaded += 1
        except Exception as e:
            pass  # File might not exist

    # 2. From DB (persisted peers)
    try:
        conn = database.get_db_connection()
        rows = conn.execute("SELECT url, alias, version, last_seen, failures, latency_ms FROM peers").fetchall()
        conn.close()
        for r in rows:
            url = r["url"].rstrip("/")
            if url != MY_NODE_URL.rstrip("/"):
                with PEER_LOCK:
                    if url not in PEERS:
                        PEERS[url] = {
                            "last_seen": r["last_seen"],
                            "latency_ms": r["latency_ms"] or 0,
                            "version": r["version"] or "?",
                            "failures": r["failures"] or 0,
                            "alias": r["alias"] or ""
                        }
                        loaded += 1
    except Exception as e:
        print(f"[P2P] Error loading DB peers: {e}")

    print(f"[P2P] Semillas cargadas: {loaded}")

def save_peers_to_db():
    """Persist current peer list to SQLite"""
    try:
        conn = database.get_db_connection()
        with PEER_LOCK:
            for url, info in PEERS.items():
                conn.execute("""
                    INSERT OR REPLACE INTO peers (url, alias, version, last_seen, failures, latency_ms)
                    VALUES (?, ?, ?, ?, ?, ?)
                """, (url, info.get("alias", ""), info.get("version", "?"),
                      info.get("last_seen", ""), info.get("failures", 0),
                      info.get("latency_ms", 0)))
        conn.commit()
        conn.close()
    except Exception as e:
        print(f"[P2P] Error saving peers: {e}")

def ping_peer(url):
    """Ping a peer and return latency or None if unreachable"""
    try:
        start = time.time()
        res = requests.get(f"{url}/api/p2p/heartbeat", timeout=3)
        latency = round((time.time() - start) * 1000, 1)
        if res.status_code == 200:
            data = res.json()
            return {"latency_ms": latency, "version": data.get("version", "?"), "alias": data.get("alias", "")}
    except:
        pass
    return None

def announce_to_peer(peer_url):
    """Announce ourselves to a peer and receive their peer list (gossip)"""
    try:
        res = requests.post(f"{peer_url}/api/p2p/announce", json={
            "url": MY_NODE_URL,
            "version": CURRENT_VERSION,
            "alias": "Nodo Tairuma"
        }, timeout=4)
        if res.status_code == 200:
            data = res.json()
            # Gossip: learn new peers from this node
            new_peers = data.get("peers", [])
            added = 0
            with PEER_LOCK:
                for p in new_peers:
                    p_url = p.get("url", "").rstrip("/")
                    if p_url and p_url != MY_NODE_URL.rstrip("/") and p_url not in PEERS:
                        PEERS[p_url] = {"last_seen": None, "latency_ms": 0, "version": "?", "failures": 0, "alias": ""}
                        added += 1
            if added > 0:
                print(f"[P2P] Descubiertos {added} nuevos peers via {peer_url}")
            return True
    except:
        pass
    return False

def p2p_background_loop():
    """Background thread: heartbeat, announce, prune"""
    cycle = 0
    while True:
        time.sleep(HEARTBEAT_INTERVAL)
        cycle += 1

        # --- HEARTBEAT: Ping all known peers ---
        with PEER_LOCK:
            urls = list(PEERS.keys())

        for url in urls:
            result = ping_peer(url)
            with PEER_LOCK:
                if url not in PEERS:
                    continue
                if result:
                    PEERS[url]["last_seen"] = datetime.now().isoformat()
                    PEERS[url]["latency_ms"] = result["latency_ms"]
                    PEERS[url]["version"] = result["version"]
                    PEERS[url]["alias"] = result.get("alias", "")
                    PEERS[url]["failures"] = 0
                else:
                    PEERS[url]["failures"] = PEERS[url].get("failures", 0) + 1

        # --- ANNOUNCE: Every 2 cycles (60s), announce to all peers ---
        if cycle % 2 == 0:
            for url in urls:
                announce_to_peer(url)

        # --- PRUNE: Every 4 cycles (120s), remove dead peers ---
        if cycle % 4 == 0:
            with PEER_LOCK:
                dead = [url for url, info in PEERS.items() if info.get("failures", 0) >= MAX_PEER_FAILURES]
                for url in dead:
                    del PEERS[url]
                    print(f"[P2P] Peer podado (sin respuesta): {url}")

            # Also sync ledger with alive peers
            my_hash = database.get_ledger_hash()
            my_count = database.get_transaction_count()
            for url in urls:
                with PEER_LOCK:
                    if url not in PEERS or PEERS[url].get("failures", 0) > 0:
                        continue
                try:
                    res = requests.get(f"{url}/api/network/status", timeout=3)
                    if res.status_code == 200:
                        data = res.json()
                        peer_hash = data.get("ledger_hash")
                        if peer_hash and peer_hash != my_hash:
                            peer_count = data.get("tx_count", 0)
                            if peer_count > my_count:
                                print(f"[P2P] ⚠️ Nodo {url} tiene más historia ({peer_count} vs {my_count})")
                except:
                    pass

            # Persist peers
            save_peers_to_db()

        alive_count = sum(1 for info in PEERS.values() if info.get("failures", 0) == 0 and info.get("last_seen"))
        if cycle % 2 == 0:
            print(f"[P2P] Red: {alive_count} peers activos de {len(PEERS)} conocidos")

def startup_announce():
    """Announce to all known seeds immediately on startup (runs in background thread)"""
    time.sleep(3)  # Give the server a moment to start
    with PEER_LOCK:
        urls = list(PEERS.keys())
    if not urls:
        print("[P2P] No hay semillas conocidas para anunciarse.")
        return
    print(f"[P2P] Anunciándome a {len(urls)} semilla(s)...")
    for url in urls:
        ok = announce_to_peer(url)
        if ok:
            print(f"[P2P] ✅ Conectado a {url}")
        else:
            print(f"[P2P] ❌ No se pudo conectar a {url}")
    # Also ping them to get fresh status
    for url in urls:
        result = ping_peer(url)
        if result:
            with PEER_LOCK:
                if url in PEERS:
                    PEERS[url]["last_seen"] = datetime.now().isoformat()
                    PEERS[url]["latency_ms"] = result["latency_ms"]
                    PEERS[url]["version"] = result["version"]
                    PEERS[url]["alias"] = result.get("alias", "")
                    PEERS[url]["failures"] = 0
    save_peers_to_db()
    alive = sum(1 for info in PEERS.values() if info.get("failures", 0) == 0 and info.get("last_seen"))
    print(f"[P2P] Arranque completo: {alive} peers activos")

# Start P2P background thread
threading.Thread(target=p2p_background_loop, daemon=True).start()
# Initialize Blockchain (Genesis)
blockchain.blockchain_manager.create_genesis_block()
# Announce to seeds on startup
threading.Thread(target=startup_announce, daemon=True).start()

# --- P2P API ROUTES ---

import re
from collections import defaultdict
_announce_rate = defaultdict(list)  # IP -> [timestamps]
MAX_PEERS = 100
MAX_ANNOUNCES_PER_MIN = 10

@app.post("/api/p2p/announce")
def p2p_announce(node_data: dict, request_obj=None):
    """A peer announces itself. We register it and return our peer list (gossip exchange)."""
    url = node_data.get("url", "").rstrip("/")
    version = node_data.get("version", "?")
    alias = node_data.get("alias", "")
    
    # Security: Validate URL format
    if not url or not re.match(r'^https?://[\w.-]+(:\d+)?(/.*)?$', url):
        return {"status": "rejected", "message": "URL inválida"}
    
    # Security: Rate limit announces per source URL
    now = time.time()
    _announce_rate[url] = [t for t in _announce_rate[url] if now - t < 60]
    if len(_announce_rate[url]) >= MAX_ANNOUNCES_PER_MIN:
        return {"status": "rate_limited", "message": "Demasiados anuncios. Espera un minuto."}
    _announce_rate[url].append(now)
    
    if url != MY_NODE_URL.rstrip("/"):
        with PEER_LOCK:
            # Security: Cap peer list size
            if url not in PEERS and len(PEERS) >= MAX_PEERS:
                return {"status": "full", "message": "Lista de peers llena"}
            PEERS[url] = {
                "last_seen": datetime.now().isoformat(),
                "latency_ms": 0,
                "version": version,
                "failures": 0,
                "alias": alias[:50]  # Limit alias length
            }
    
    # Return our known peers (gossip)
    with PEER_LOCK:
        peer_list = [{"url": u, "alias": info.get("alias", ""), "version": info.get("version", "?")}
                     for u, info in PEERS.items() if info.get("failures", 0) < MAX_PEER_FAILURES]
    
    return {"status": "welcome", "message": "Bienvenido al Bosque", "peers": peer_list}

@app.get("/api/p2p/heartbeat")
def p2p_heartbeat():
    """Quick ping to check if node is alive"""
    return {"status": "alive", "version": CURRENT_VERSION, "alias": "Nodo Tairuma", "timestamp": datetime.now().isoformat()}

@app.get("/api/p2p/peers")
def p2p_get_peers():
    """Return list of known active peers for client discovery"""
    with PEER_LOCK:
        active = [
            {
                "url": url,
                "alias": info.get("alias", ""),
                "version": info.get("version", "?"),
                "latency_ms": info.get("latency_ms", 0),
                "last_seen": info.get("last_seen", ""),
                "online": info.get("failures", 0) == 0 and info.get("last_seen") is not None
            }
            for url, info in PEERS.items()
            if info.get("failures", 0) < MAX_PEER_FAILURES
        ]
    return {"my_url": MY_NODE_URL, "peers": active, "total": len(active)}

# Keep old endpoints for backward compatibility
@app.post("/api/network/register")
def register_node(node_data: dict):
    """Legacy endpoint — redirects to p2p/announce"""
    return p2p_announce(node_data)

@app.get("/api/network/status")
def network_status():
    with PEER_LOCK:
        alive = sum(1 for info in PEERS.values() if info.get("failures", 0) == 0 and info.get("last_seen"))
    return {
        "version": CURRENT_VERSION,
        "ledger_hash": database.get_ledger_hash(),
        "tx_count": database.get_transaction_count(),
        "known_peers": len(PEERS),
        "active_peers": alive,
        "my_url": MY_NODE_URL,
        "is_founder": False
    }

@app.get("/api/network/peers")
def get_message_peers():
    """Legacy endpoint — returns peer URLs"""
    with PEER_LOCK:
        return [url for url, info in PEERS.items() if info.get("failures", 0) < MAX_PEER_FAILURES]

# Inicializar BD y Red al arrancar
database.init_db()
load_seed_nodes()

# Montar archivos estáticos (Frontend)
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse

# API Routes first...

# Modelos
class Mensaje(BaseModel):
    remitente: str
    destinatario: Optional[str] = None # Para chats privados (alias)
    grupo_id: Optional[int] = None     # Para chats de grupo
    mensaje: str

class NuevoGrupo(BaseModel):
    nombre: str
    descripcion: str
    creador_alias: str
    tipo: str = "Publico"

class UnirseGrupo(BaseModel):
    grupo_id: int
    usuario_alias: str

class Producto(BaseModel):
    alias: str
    producto: str
    precio: float
    descripcion: str

class Transferencia(BaseModel):
    remitente_alias: str
    destinatario_alias: str
    cantidad: float

# --- MODELOS DE AUTH ---
class LoginRequest(BaseModel):
    semilla: str

class RegisterRequest(BaseModel):
    codigo_invitacion: str
    alias_deseado: str

class InviteRequest(BaseModel):
    creador_alias: str

# --- MODELOS DE GOBERNANZA ---
class Propuesta(BaseModel):
    autor: str
    titulo: str
    descripcion: str

class Voto(BaseModel):
    propuesta_id: int
    usuario_alias: str
    opcion: str # PRO, CON

class VotoReputacion(BaseModel):
    votante_alias: str
    objetivo_alias: str
    # es_positivo: bool = True # Simplificado a solo "Sembrar Confianza" (Positivo) por ahora

# --- MODELOS DE MEMBRESÍA & ZONA ---
class UpdateZona(BaseModel):
    alias: str
    zona: str

class Nodo(BaseModel):
    ip: str
    nombre: str

# --- ENDPOINTS ---



# --- LÓGICA DE MINERÍA COMUNITARIA (Sostenimiento) ---
# Recompensas automáticas por mantener el nodo activo

RECOMANSA_UPTIME = 0.5  # Numas por cada ciclo de red
INTERVALO_MINERIA = 3600 # 1 hora (Tiempo de Red)
USUARIO_MINERO = "Numa_Fundador"

def mineria_sostenimiento():
    """Hilo de fondo que acredita recompensas por uptime cada ciclo"""
    print(f"[Mineria] Sostenimiento iniciado para {USUARIO_MINERO}")
    
    # Registrar el Nodo Local al iniciar la minería
    try:
        conn = database.get_db_connection()
        conn.execute("INSERT OR REPLACE INTO nodos_red (ip, nombre, estado) VALUES (?, ?, ?)", 
                    ("127.0.0.1", "Mi Nodo Local (Tú)", "Activo"))
        conn.commit()
        conn.close()
        print("[Red] Tu nodo ha sido registrado visiblemente en el Bosque.")
    except Exception as e:
        print(f"[Red] Error registrando nodo local: {e}")

    while True:
        try:
            time.sleep(INTERVALO_MINERIA)
            conn = database.get_db_connection()
            user = conn.execute("SELECT * FROM usuarios WHERE alias = ?", (USUARIO_MINERO,)).fetchone()
            
            if user:
                # Acreditar recompensa
                conn.execute("UPDATE usuarios SET saldo = saldo + ? WHERE id = ?", (RECOMANSA_UPTIME, user['id']))
                conn.execute("INSERT INTO transacciones (usuario_id, descripcion, cantidad) VALUES (?, ?, ?)",
                            (user['id'], "Recompensa por Sostenimiento (Uptime)", RECOMANSA_UPTIME))
                # Log de auditoría
                conn.execute("INSERT INTO actividad_log (evento) VALUES (?)", 
                            (f"Mineria: {USUARIO_MINERO} ha generado {RECOMANSA_UPTIME} Numas.",))
                conn.commit()
                print(f"[Caja] 💰 CLINK! +{RECOMANSA_UPTIME} Numas minados por {USUARIO_MINERO}")
            
            conn.close()
        except Exception as e:
            print(f"[Error] Hilo de mineria: {e}")

# Iniciar minería en un hilo separado
thread_mineria = threading.Thread(target=mineria_sostenimiento, daemon=True)
thread_mineria.start()

# --- ENDPOINTS ---

# --- ENDPOINTS AUTH & SECURITY ---

@app.post("/api/auth/login")
async def login(req: LoginRequest):
    conn = database.get_db_connection()
    # Buscar usuarios (como no guardamos la semilla plana, tenemos que iterar o usar el alias si el login pidiera alias)
    # Pero para login SOLO con semilla, es ineficiente hashear todas. 
    # Mejor pedir Alias + Semilla o (por simplicidad de UX) probamos hashear la semilla y buscar ese hash.
    # Dado que el hash es determinista con el salt estático (prototipo), podemos buscar por hash.
    
    pass_hash = hash_password(req.semilla.strip())
    user = conn.execute("SELECT * FROM usuarios WHERE password_hash = ?", (pass_hash,)).fetchone()
    
    # Si el Fundador no tiene hash, permitir migración con la frase original
    # (REMOVED: backdoor login eliminated for security)

    conn.close()
    
    if not user:
        raise HTTPException(status_code=401, detail="Semilla incorrecta o no reconocida.")
    
    token = create_token(user["alias"])
    return {
        "success": True,
        "alias": user["alias"],
        "id": user["id"],
        "token": token,
        "mensaje": "Bienvenido de nuevo al bosque."
    }

@app.post("/api/auth/register")
async def register(req: RegisterRequest):
    conn = database.get_db_connection()
    
    # 1. Validar invitación
    invitacion = conn.execute("SELECT * FROM invitaciones WHERE codigo = ? AND estado = 'Pendiente'", (req.codigo_invitacion,)).fetchone()
    if not invitacion:
        conn.close()
        raise HTTPException(status_code=400, detail="Código de invitación inválido o ya usado.")
        
    # 2. Validar alias único
    if conn.execute("SELECT 1 FROM usuarios WHERE alias = ?", (req.alias_deseado,)).fetchone():
        conn.close()
        raise HTTPException(status_code=400, detail="Ese alias ya está ocupado.")
        
    # 3. Generar Seguridad
    semilla = generate_seed()
    pass_hash = hash_password(semilla)
    user_id = hashlib.sha256(req.alias_deseado.encode()).hexdigest()[:12]
    
    try:
        # Crear usuario con invited_by
        conn.execute("INSERT INTO usuarios (id, alias, frase_inicial, saldo, password_hash, invited_by) VALUES (?, ?, ?, ?, ?, ?)",
                    (user_id, req.alias_deseado, "Nueva semilla brotada.", 100.0, pass_hash, invitacion['creador_alias'])) # Bono bienvenida
        
        # Marcar invitación como usada
        conn.execute("UPDATE invitaciones SET estado = 'Usada', usado_por_alias = ? WHERE id = ?", 
                    (req.alias_deseado, invitacion['id']))

        # Recompensar al invitador (Referral Reward)
        conn.execute("UPDATE usuarios SET saldo = saldo + 50 WHERE alias = ?", (invitacion['creador_alias'],))
        
        conn.commit()
        conn.close()

        # BLOCKCHAIN HOOK: Register
        try:
            print(f"[DEBUG] Blockchain hook for {req.alias_deseado}")
            tx_data = {
                "event": "register", 
                "alias": req.alias_deseado, 
                "amount": 100 
            }
            # Add bonus if invited
            if invitacion:
                tx_data["amount"] += 50
                tx_data["invited_by"] = invitacion['creador_alias']

            blockchain.blockchain_manager.create_new_block([tx_data])
            print("[DEBUG] Block created")
        except Exception as e:
            print(f"[Blockchain] Error creating block for register: {e}")

        token = create_token(req.alias_deseado)
        return {
            "success": True,
            "alias": req.alias_deseado,
            "semilla": semilla, # SE MUESTRA UNA SOLA VEZ
            "token": token,
            "mensaje": "Guarda estas 12 palabras en un lugar seguro. Son tu única llave."
        }
    except Exception as e:
        conn.rollback()
        raise HTTPException(status_code=500, detail=f"Error en el registro: {str(e)}")
    finally:
        conn.close()

@app.post("/api/invitaciones/crear")
async def crear_invitacion(req: InviteRequest):
    conn = database.get_db_connection()
    # Generar código tipo NUMA-XXXX
    suffix = secrets.token_hex(2).upper()
    codigo = f"NUMA-{suffix}"
    
    conn.execute("INSERT INTO invitaciones (codigo, creador_alias) VALUES (?, ?)", 
                (codigo, req.creador_alias))
    conn.commit()
    conn.close()
    return {"codigo": codigo}

@app.get("/api/invitaciones/mis/{alias}")
async def get_mis_invitaciones(alias: str):
    conn = database.get_db_connection()
    invs = conn.execute("SELECT * FROM invitaciones WHERE creador_alias = ? ORDER BY fecha_creacion DESC", (alias,)).fetchall()
    conn.close()
    return [dict(i) for i in invs]

@app.get("/api/referidos/{alias}")
async def get_referidos(alias: str):
    conn = database.get_db_connection()
    # Get users invited by this alias
    refs = conn.execute("SELECT alias FROM usuarios WHERE invited_by = ?", (alias,)).fetchall()
    simplified_refs = [{"alias": r["alias"]} for r in refs]
    conn.close()
    return simplified_refs


@app.get("/api/numa/{alias}")
async def get_numa(alias: str, referrer: Optional[str] = None):
    conn = database.get_db_connection()
    user = conn.execute("SELECT * FROM usuarios WHERE alias = ?", (alias,)).fetchone()
    
    if not user:
        # CREACIÓN DE NUEVO USUARIO CON INCENTIVO DE CRECIMIENTO
        user_id = hashlib.sha256(alias.encode()).hexdigest()[:12]
        saldo_inicial = 100.0
        bono = 10.0 if referrer else 0.0 # Bono por unirse mediante alguien
        
        conn.execute("INSERT INTO usuarios (id, alias, frase_inicial, saldo) VALUES (?, ?, ?, ?)",
                    (user_id, alias, "Nueva semilla en el bosque Numa.", saldo_inicial + bono))
        
        # Registrar transacción inicial
        conn.execute("INSERT INTO transacciones (usuario_id, descripcion, cantidad) VALUES (?, ?, ?)",
                    (user_id, "Saldo Inicial de Bienvenida", saldo_inicial))
        
        if referrer:
            # Recompensar al reclutador
            reclutador = conn.execute("SELECT * FROM usuarios WHERE alias = ?", (referrer,)).fetchone()
            if reclutador:
                recompensa_reclutador = 10.0
                conn.execute("UPDATE usuarios SET saldo = saldo + ? WHERE id = ?", (recompensa_reclutador, reclutador['id']))
                conn.execute("INSERT INTO transacciones (usuario_id, descripcion, cantidad) VALUES (?, ?, ?)",
                            (reclutador['id'], f"Bono por reclutar a {alias}", recompensa_reclutador))
                # Ganar reputación por crecimiento
                conn.execute("UPDATE reputacion SET semillas = semillas + 5 WHERE usuario_alias = ?", (referrer,))
                
                # Registrar el bono en la cuenta del nuevo
                conn.execute("INSERT INTO transacciones (usuario_id, descripcion, cantidad) VALUES (?, ?, ?)",
                            (user_id, f"Bono de Crecimiento (Referido por {referrer})", bono))
                
                # Log de auditoría
                conn.execute("INSERT INTO actividad_log (evento) VALUES (?)", 
                            (f"Nuevo Miembro: {alias} se une al bosque (Referido por {referrer})",))
        else:
            conn.execute("INSERT INTO actividad_log (evento) VALUES (?)", 
                        (f"Nuevo Miembro: {alias} germina en el bosque.",))
        
        conn.commit()
        
        # BLOCKCHAIN HOOK: Register
        try:
            tx_data = {"event": "register", "alias": alias, "amount": 1000 if not referrer else 1100}
            blockchain.blockchain_manager.create_new_block([tx_data])
        except Exception as e:
            print(f"[Blockchain] Error creating block for register: {e}")

        user = conn.execute("SELECT * FROM usuarios WHERE alias = ?", (alias,)).fetchone()
    
    transacciones = conn.execute("SELECT * FROM transacciones WHERE usuario_id = ? ORDER BY fecha DESC LIMIT 10", 
                                (user['id'],)).fetchall()
    conn.close()
    
    return {
        "id": user["id"],
        "alias": user["alias"],
        "frase": user["frase_inicial"],
        "saldo": user["saldo"],
        "historial": [dict(t) for t in transacciones]
    }

@app.post("/api/transferir")
async def transferir(t: Transferencia, current_user: str = Depends(require_auth)):
    if current_user != t.remitente_alias:
        raise HTTPException(status_code=403, detail="No puedes transferir desde otra cuenta.")
    if t.cantidad <= 0:
        raise HTTPException(status_code=400, detail="La cantidad debe ser mayor a 0")
        
    conn = database.get_db_connection()
    try:
        remitente = conn.execute("SELECT * FROM usuarios WHERE alias = ?", (t.remitente_alias,)).fetchone()
        if not remitente or remitente['saldo'] < t.cantidad:
            raise HTTPException(status_code=400, detail="Saldo insuficiente o usuario no encontrado")
            
        destinatario = conn.execute("SELECT * FROM usuarios WHERE alias = ?", (t.destinatario_alias,)).fetchone()
        if not destinatario:
            raise HTTPException(status_code=404, detail="Destinatario no encontrado")
            
        conn.execute("UPDATE usuarios SET saldo = saldo - ? WHERE id = ?", (t.cantidad, remitente['id']))
        conn.execute("UPDATE usuarios SET saldo = saldo + ? WHERE id = ?", (t.cantidad, destinatario['id']))
        
        conn.execute("INSERT INTO transacciones (usuario_id, descripcion, cantidad) VALUES (?, ?, ?)",
                    (remitente['id'], f"Envío a {t.destinatario_alias}", -t.cantidad))
        conn.execute("INSERT INTO transacciones (usuario_id, descripcion, cantidad) VALUES (?, ?, ?)",
                    (destinatario['id'], f"Recibido de {t.remitente_alias}", t.cantidad))
        
        conn.execute("INSERT INTO actividad_log (evento) VALUES (?)", 
                    (f"Transferencia P2P: {t.remitente_alias} -> {t.destinatario_alias} ({t.cantidad})"))
        
        conn.commit()
        
        # BLOCKCHAIN HOOK: Transfer
        try:
            tx_data = {
                "event": "transfer", 
                "sender": t.remitente_alias, 
                "recipient": t.destinatario_alias, 
                "amount": t.cantidad,
                "memo": f"Transferencia de {t.remitente_alias} a {t.destinatario_alias}"
            }
            blockchain.blockchain_manager.create_new_block([tx_data])
        except Exception as e:
            print(f"[Blockchain] Error creating block for transfer: {e}")

        return {"status": "success", "nuevo_saldo": remitente['saldo'] - t.cantidad}
    except Exception as e:
        conn.rollback()
        raise e
    finally:
        conn.close()

@app.get("/api/transacciones/{alias}")
async def get_transacciones(alias: str):
    conn = database.get_db_connection()
    user = conn.execute("SELECT id FROM usuarios WHERE alias = ?", (alias,)).fetchone()
    if not user:
        conn.close()
        raise HTTPException(status_code=404, detail="Usuario no encontrado")
    txs = conn.execute("""
        SELECT descripcion, cantidad, fecha FROM transacciones 
        WHERE usuario_id = ? ORDER BY fecha DESC LIMIT 20
    """, (user['id'],)).fetchall()
    conn.close()
    return [dict(t) for t in txs]

@app.get("/api/tesoro")
async def get_tesoro():
    conn = database.get_db_connection()
    # Support both new and old branding during transition
    tesoro = conn.execute("SELECT saldo FROM usuarios WHERE alias = 'Numa_Tesoro'").fetchone()
    if not tesoro:
        tesoro = conn.execute("SELECT saldo FROM usuarios WHERE alias = 'Tairuma_Tesoro'").fetchone()
    
    if not tesoro:
        tesoro = {'saldo': 0.0} # Fallback
        
    log = conn.execute("SELECT evento, fecha FROM actividad_log ORDER BY fecha DESC LIMIT 5").fetchall()
    conn.close()
    return {
        "saldo": tesoro['saldo'],
        "eventos": [dict(e) for e in log]
    }

@app.get("/api/svcdt")
async def get_svcdt():
    conn = database.get_db_connection()
    transacciones = conn.execute("SELECT SUM(ABS(cantidad)) as total FROM transacciones").fetchone()
    total = transacciones['total'] or 0
    factor = 1.0 + (total / 10000.0) 
    conn.close()
    return {
        "valor": round(factor, 2),
        "valor_eur": NUMA_EUR_VALUE,
        "referencia": f"1 Numa = {NUMA_EUR_VALUE}€"
    }

@app.get("/api/mercado")
async def get_mercado():
    conn = database.get_db_connection()
    # Join with users for Zone and Reputation for Trust
    productos = conn.execute('''
        SELECT m.*, u.zona, IFNULL(r.semillas, 0) as semillas_confianza
        FROM mercado m 
        LEFT JOIN usuarios u ON m.alias = u.alias
        LEFT JOIN reputacion r ON m.alias = r.usuario_alias
        ORDER BY m.fecha DESC
    ''').fetchall()
    conn.close()
    return [dict(p) for p in productos]

@app.post("/api/mercado")
async def add_mercado(p: Producto):
    conn = database.get_db_connection()
    conn.execute("INSERT INTO mercado (alias, producto, precio, descripcion) VALUES (?, ?, ?, ?)",
                (p.alias, p.producto, p.precio, p.descripcion))
    conn.commit()
    conn.close()
    return {"status": "success"}

@app.get("/api/chat/mensajes")
async def get_mensajes(alias: str, contexto: str = "global", id_contexto: str = None):
    conn = database.get_db_connection()
    
    if contexto == "global":
        # Chat Global (Grupo ID 1 por defecto o destinatario 'Global')
        mensajes = conn.execute("SELECT * FROM chat WHERE destinatario = 'Global' OR grupo_id = 1 ORDER BY fecha DESC LIMIT 50").fetchall()
    
    elif contexto == "grupo":
        # Chat de Grupo
        mensajes = conn.execute("SELECT * FROM chat WHERE grupo_id = ? ORDER BY fecha DESC LIMIT 50", (id_contexto,)).fetchall()
        
    elif contexto == "privado":
        # Chat Privado (Yo <-> Otro)
        mensajes = conn.execute('''
            SELECT * FROM chat 
            WHERE (remitente = ? AND destinatario = ?) 
               OR (remitente = ? AND destinatario = ?)
            ORDER BY fecha DESC LIMIT 50
        ''', (alias, id_contexto, id_contexto, alias)).fetchall()
        
    else:
        mensajes = []

    conn.close()
    return [dict(m) for m in mensajes]

@app.post("/api/chat")
async def post_chat(m: Mensaje, current_user: str = Depends(require_auth)):
    if current_user != m.remitente:
        raise HTTPException(status_code=403, detail="No puedes enviar mensajes como otro usuario.")
    conn = database.get_db_connection()
    
    # Si es grupo, guardamos grupo_id. Si es privado, destinatario.
    # Si no se especifica, asumimos Global.
    
    if m.grupo_id:
        # Mensaje de Grupo
        conn.execute("INSERT INTO chat (remitente, grupo_id, mensaje) VALUES (?, ?, ?)",
                    (m.remitente, m.grupo_id, m.mensaje))
    elif m.destinatario:
         # Mensaje Privado o Global explicito
        conn.execute("INSERT INTO chat (remitente, destinatario, mensaje) VALUES (?, ?, ?)",
                    (m.remitente, m.destinatario, m.mensaje))
    else:
        # Fallback a Global (viejo)
        conn.execute("INSERT INTO chat (remitente, destinatario, mensaje) VALUES (?, 'Global', ?)",
                    (m.remitente, m.mensaje))
                    
    conn.commit()
    conn.close()
    return {"status": "success"}

class EdicionMensaje(BaseModel):
    remitente: str
    nuevo_mensaje: str

@app.delete("/api/chat/{msg_id}")
async def delete_chat(msg_id: int, remitente: str):
    conn = database.get_db_connection()
    # Verificar propiedad
    msg = conn.execute("SELECT * FROM chat WHERE id = ?", (msg_id,)).fetchone()
    if not msg:
        conn.close()
        raise HTTPException(status_code=404, detail="Mensaje no encontrado")
    
    if msg['remitente'] != remitente:
        conn.close()
        raise HTTPException(status_code=403, detail="No puedes borrar mensajes de otros")
        
    conn.execute("DELETE FROM chat WHERE id = ?", (msg_id,))
    conn.commit()
    conn.close()
    return {"status": "deleted"}

@app.put("/api/chat/{msg_id}")
async def edit_chat(msg_id: int, e: EdicionMensaje):
    conn = database.get_db_connection()
    # Verificar propiedad
    msg = conn.execute("SELECT * FROM chat WHERE id = ?", (msg_id,)).fetchone()
    if not msg:
        conn.close()
        raise HTTPException(status_code=404, detail="Mensaje no encontrado")
    
    if msg['remitente'] != e.remitente:
        conn.close()
        raise HTTPException(status_code=403, detail="No puedes editar mensajes de otros")
        
    conn.execute("UPDATE chat SET mensaje = ? WHERE id = ?", (e.nuevo_mensaje, msg_id))
    conn.commit()
    conn.close()
    return {"status": "updated"}

# --- ENDPOINTS GRUPOS ---

@app.get("/api/grupos")
async def get_grupos():
    conn = database.get_db_connection()
    grupos = conn.execute("SELECT * FROM grupos").fetchall()
    conn.close()
    return [dict(g) for g in grupos]

@app.post("/api/grupos")
async def crear_grupo(g: NuevoGrupo):
    conn = database.get_db_connection()
    try:
        cursor = conn.cursor()
        cursor.execute("INSERT INTO grupos (nombre, descripcion, tipo, creador_alias) VALUES (?, ?, ?, ?)",
                      (g.nombre, g.descripcion, g.tipo, g.creador_alias))
        grupo_id = cursor.lastrowid
        
        # Añadir al creador como miembro
        cursor.execute("INSERT INTO miembros_grupo (grupo_id, usuario_alias, rol) VALUES (?, ?, 'Admin')",
                      (grupo_id, g.creador_alias))
        
        conn.commit()
        return {"status": "success", "grupo_id": grupo_id}
    except Exception as e:
        conn.rollback()
        raise HTTPException(status_code=400, detail=str(e))
    finally:
        conn.close()

@app.post("/api/grupos/unirse")
async def unirse_grupo(u: UnirseGrupo):
    conn = database.get_db_connection()
    conn.execute("INSERT OR IGNORE INTO miembros_grupo (grupo_id, usuario_alias) VALUES (?, ?)",
                (u.grupo_id, u.usuario_alias))
    conn.commit()
    conn.close()

# NOTE: EdicionGrupo model + DELETE/PUT grupo endpoints are defined below in GROUPS MANAGEMENT section (line ~1173)


# NOTE: Propuesta, Voto, VotoReputacion, Nodo models defined above in MODELOS section

# --- ENDPOINTS DE REPUTACIÓN (Semillas de Confianza) ---

@app.get("/api/reputacion/{alias}")
async def get_reputacion(alias: str):
    conn = database.get_db_connection()
    rep = conn.execute("SELECT * FROM reputacion WHERE usuario_alias = ?", (alias,)).fetchone()
    conn.close()
    if not rep:
        return {"usuario_alias": alias, "semillas": 0}
    return dict(rep)

@app.post("/api/reputacion/votar")
async def votar_reputacion(v: VotoReputacion, current_user: str = Depends(require_auth)):
    if current_user != v.votante_alias:
        raise HTTPException(status_code=403, detail="No puedes votar como otro usuario.")
    if v.votante_alias == v.objetivo_alias:
        raise HTTPException(status_code=400, detail="No puedes sembrar confianza en ti mismo.")
        
    conn = database.get_db_connection()
    
    # Rate Limiting: 1 voto por par (votante->objetivo) cada 24 horas
    # SQLite datetime is text, so we use modifiers
    last_vote = conn.execute('''
        SELECT fecha FROM votos_confianza_log 
        WHERE votante = ? AND votado = ? 
        ORDER BY fecha DESC LIMIT 1
    ''', (v.votante_alias, v.objetivo_alias)).fetchone()
    
    if last_vote:
        # Check time diff
        last_dt = datetime.strptime(last_vote['fecha'], "%Y-%m-%d %H:%M:%S")
        if (datetime.now() - last_dt).total_seconds() < 86400: # 24 hours
             conn.close()
             raise HTTPException(status_code=400, detail="Ya has sembrado confianza en este ser hoy. Vuelve mañana.")

    try:
        # Registrar voto
        conn.execute("INSERT INTO votos_confianza_log (votante, votado) VALUES (?, ?)", (v.votante_alias, v.objetivo_alias))
        
        # Actualizar reputación (Upsert-like logic)
        existing = conn.execute("SELECT 1 FROM reputacion WHERE usuario_alias = ?", (v.objetivo_alias,)).fetchone()
        if existing:
            conn.execute("UPDATE reputacion SET semillas = semillas + 1, votos_positivos = votos_positivos + 1 WHERE usuario_alias = ?", (v.objetivo_alias,))
        else:
            conn.execute("INSERT INTO reputacion (usuario_alias, semillas, votos_positivos) VALUES (?, 1, 1)", (v.objetivo_alias,))
            
        conn.commit()
        return {"status": "success", "mensaje": "Has sembrado una semilla de confianza."}
    except Exception as e:
        conn.rollback()
        raise HTTPException(status_code=500, detail=str(e))
    finally:
        conn.close()

# --- ENDPOINTS DE PERFIL (ZONA) ---

@app.put("/api/perfil/zona")
async def update_zona(u: UpdateZona):
    conn = database.get_db_connection()
    # Validate user exists
    user = conn.execute("SELECT * FROM usuarios WHERE alias = ?", (u.alias,)).fetchone()
    if not user:
        conn.close()
        raise HTTPException(status_code=404, detail="Usuario no encontrado")
        
    conn.execute("UPDATE usuarios SET zona = ? WHERE alias = ?", (u.zona, u.alias))
    conn.commit()
    conn.close()
    return {"status": "updated", "zona": u.zona}

# --- ENDPOINTS DE RED DE BOSQUE (Multi-Nodo) ---

@app.get("/api/red/nodos")
async def get_nodos():
    conn = database.get_db_connection()
    nodos = conn.execute("SELECT * FROM nodos_red").fetchall()
    conn.close()
    return [dict(n) for n in nodos]

@app.post("/api/red/conectar")
async def conectar_nodo(n: Nodo):
    conn = database.get_db_connection()
    conn.execute("INSERT OR REPLACE INTO nodos_red (ip, nombre) VALUES (?, ?)", (n.ip, n.nombre))
    conn.commit()
    conn.close()
    return {"status": "conectado"}

# --- BLOCKCHAIN ENDPOINTS ---

@app.get("/api/blockchain/status")
def get_blockchain_status():
    """Returns basic stats about the blockchain"""
    last_block = blockchain.blockchain_manager.get_last_block()
    valid = blockchain.blockchain_manager.is_chain_valid()
    return {
        "height": last_block["index"] + 1 if last_block else 0,
        "last_hash": last_block["hash"] if last_block else "",
        "valid": valid
    }

@app.get("/api/blockchain/blocks")
def get_blockchain_blocks(limit: int = 50):
    """Returns the chain (limited)"""
    chain = blockchain.blockchain_manager.get_chain()
    return chain[-limit:]

# --- ENDPOINTS DE GOBERNANZA (Asegurando que existan los anteriores) ---

@app.get("/api/gobernanza")
async def get_propuestas():
    conn = database.get_db_connection()
    propuestas = conn.execute("SELECT * FROM propuestas ORDER BY fecha DESC").fetchall()
    conn.close()
    return [dict(p) for p in propuestas]

@app.post("/api/gobernanza/propuesta")
async def crear_propuesta(p: Propuesta):
    conn = database.get_db_connection()
    try:
        # Check if columns exist (simple heuristic/robustness)
        # Just insert. If columns like votos_pro allow null or have defaults, it works.
        conn.execute("INSERT INTO propuestas (autor, titulo, descripcion, votos_pro, votos_con, estado) VALUES (?, ?, ?, 0, 0, 'Activa')",
                    (p.autor, p.titulo, p.descripcion))
        
        # Log de gobernanza
        conn.execute("INSERT INTO actividad_log (evento) VALUES (?)", 
                    (f"Gobernanza: Nueva propuesta '{p.titulo}' por {p.autor}",))
        conn.commit()
        return {"status": "success"}
    except Exception as e:
        conn.rollback()
        # Log error to console for debugging
        print(f"Error creating proposal: {e}")
        raise HTTPException(status_code=500, detail=f"Error BD: {str(e)}")
    finally:
        conn.close()

class EdicionPropuesta(BaseModel):
    autor: str
    nuevo_titulo: str
    nueva_descripcion: str

@app.put("/api/gobernanza/propuesta/{id}")
async def editar_propuesta(id: int, e: EdicionPropuesta):
    conn = database.get_db_connection()
    prop = conn.execute("SELECT * FROM propuestas WHERE id = ?", (id,)).fetchone()
    if not prop:
        conn.close()
        raise HTTPException(status_code=404, detail="Propuesta no encontrada")
        
    if prop['autor'] != e.autor:
        conn.close()
        raise HTTPException(status_code=403, detail="No es tu propuesta")
        
    conn.execute("UPDATE propuestas SET titulo = ?, descripcion = ? WHERE id = ?", 
                (e.nuevo_titulo, e.nueva_descripcion, id))
    conn.commit()
    conn.close()
    return {"status": "updated"}

@app.delete("/api/gobernanza/propuesta/{id}")
async def borrar_propuesta(id: int, autor: str):
    conn = database.get_db_connection()
    prop = conn.execute("SELECT * FROM propuestas WHERE id = ?", (id,)).fetchone()
    if not prop or prop['autor'] != autor:
        conn.close()
        raise HTTPException(status_code=403, detail="No puedes borrar esto")
    
    conn.execute("DELETE FROM propuestas WHERE id = ?", (id,))
    conn.commit()
    conn.close()
    return {"status": "deleted"}

@app.post("/api/gobernanza/votar")
async def votar(v: Voto):
    conn = database.get_db_connection()
    try:
        # Check existing vote
        existing = conn.execute("SELECT opcion FROM votos WHERE propuesta_id = ? AND usuario_alias = ?", 
                               (v.propuesta_id, v.usuario_alias)).fetchone()
        
        if existing:
            if existing['opcion'] == v.opcion:
                conn.close()
                return {"status": "success", "msg": "Voto ya registrado"}
            
            # Change vote: decrement old, increment new
            if existing['opcion'] == 'PRO':
                conn.execute("UPDATE propuestas SET votos_pro = votos_pro - 1 WHERE id = ?", (v.propuesta_id,))
            else:
                conn.execute("UPDATE propuestas SET votos_con = votos_con - 1 WHERE id = ?", (v.propuesta_id,))
            
            conn.execute("UPDATE votos SET opcion = ? WHERE propuesta_id = ? AND usuario_alias = ?", 
                        (v.opcion, v.propuesta_id, v.usuario_alias))
            
            if v.opcion == 'PRO':
                conn.execute("UPDATE propuestas SET votos_pro = votos_pro + 1 WHERE id = ?", (v.propuesta_id,))
            else:
                conn.execute("UPDATE propuestas SET votos_con = votos_con + 1 WHERE id = ?", (v.propuesta_id,))
                
        else:
            # New vote
            conn.execute("INSERT INTO votos (propuesta_id, usuario_alias, opcion) VALUES (?, ?, ?)",
                        (v.propuesta_id, v.usuario_alias, v.opcion))
            
            if v.opcion == 'PRO':
                conn.execute("UPDATE propuestas SET votos_pro = votos_pro + 1 WHERE id = ?", (v.propuesta_id,))
            else:
                conn.execute("UPDATE propuestas SET votos_con = votos_con + 1 WHERE id = ?", (v.propuesta_id,))
            
        conn.commit()
        return {"status": "success"}
    except Exception as e:
        conn.rollback()
        print(f"Error votacion: {e}")
        raise HTTPException(status_code=500, detail="Error al votar")
    finally:
        conn.close()

# --- GROUPS MANAGEMENT ---

class EdicionGrupo(BaseModel):
    creador_alias: str
    nuevo_nombre: str
    nueva_descripcion: str

@app.delete("/api/grupos/{grupo_id}")
async def delete_grupo(grupo_id: int, creador_alias: str):
    conn = database.get_db_connection()
    # Verificar propiedad
    grupo = conn.execute("SELECT * FROM grupos WHERE id = ?", (grupo_id,)).fetchone()
    if not grupo:
        conn.close()
        raise HTTPException(status_code=404, detail="Círculo no encontrado")
    
    if grupo['creador_alias'] != creador_alias and grupo['creador_alias'] != 'Numa_Fundador': # Fundador power optional
        conn.close()
        raise HTTPException(status_code=403, detail="Solo el sembrador (creador) puede podar este círculo")
        
    conn.execute("DELETE FROM grupos WHERE id = ?", (grupo_id,))
    conn.execute("DELETE FROM miembros_grupo WHERE grupo_id = ?", (grupo_id,)) # Limpiar miembros
    conn.commit()
    conn.close()
    return {"status": "deleted"}

@app.put("/api/grupos/{grupo_id}")
async def edit_grupo(grupo_id: int, e: EdicionGrupo):
    conn = database.get_db_connection()
    # Verificar propiedad
    grupo = conn.execute("SELECT * FROM grupos WHERE id = ?", (grupo_id,)).fetchone()
    if not grupo:
        conn.close()
        raise HTTPException(status_code=404, detail="Círculo no encontrado")
    
    if grupo['creador_alias'] != e.creador_alias:
        conn.close()
        raise HTTPException(status_code=403, detail="Solo el sembrador puede cuidar (editar) este círculo")
        
    conn.execute("UPDATE grupos SET nombre = ?, descripcion = ? WHERE id = ?", (e.nuevo_nombre, e.nueva_descripcion, grupo_id))
    conn.commit()
    conn.close()
    return {"status": "updated"}

# --- Mount frontend static files ---
app.mount("/", StaticFiles(directory="web", html=True), name="static")

if __name__ == "__main__":
    import uvicorn
    import os
    port = int(os.environ.get("PORT", 9500))

    print("\n" + "="*60)
    database.init_db()  # Initialize/Fix DB before starting
    print(" SISTEMA TAIRUMA V3.1 P2P: NODO ACTIVO")
    print("   Estado: Conectado al Bosque")
    print(f"   Puerto: {port}")
    print(f"   Base de datos: {database.DB_PATH}")
    print("   Auth: JWT habilitado")
    print("   Logs: Modo Produccion")
    print("="*60 + "\n")

    uvicorn.run(app, host="0.0.0.0", port=port, log_level="critical", access_log=False)
