<?php
/**
 * ICEBERG Manillas - Project Dashboard & Task Tracker v2
 * =======================================================
 * Panel de proyecto con seguimiento de tareas, temporizador
 * por tarea, estructura del proyecto, y tracking diario.
 * Subir por FTP. MySQL.
 */
date_default_timezone_set('America/Guayaquil');

$DB_HOST = 'localhost';
$DB_NAME = 'icerbger_Proyectos';
$DB_USER = 'icerbger_Dathor';
$DB_PASS = 'Quince0Tres';

try {
    $db = new PDO("mysql:host=$DB_HOST;dbname=$DB_NAME;charset=utf8mb4", $DB_USER, $DB_PASS);
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch (PDOException $e) {
    die('<div style="background:#161b22;color:#f85149;padding:2rem;font-family:monospace;text-align:center;margin:3rem auto;max-width:500px;border-radius:12px;border:1px solid #30363d"><h2>Error de conexion</h2><p>'.htmlspecialchars($e->getMessage()).'</p></div>');
}

// ========== SCHEMA ==========
$db->exec("CREATE TABLE IF NOT EXISTS tasks (
    id INT AUTO_INCREMENT PRIMARY KEY,
    owner VARCHAR(20) NOT NULL DEFAULT 'jr',
    sprint INT NOT NULL DEFAULT 1,
    task_code VARCHAR(10) NOT NULL,
    title VARCHAR(255) NOT NULL,
    description TEXT DEFAULT NULL,
    acceptance TEXT DEFAULT NULL,
    priority VARCHAR(20) DEFAULT 'media',
    status VARCHAR(30) DEFAULT 'pendiente',
    deadline DATE DEFAULT NULL,
    max_hours DECIMAL(5,1) DEFAULT 0,
    estimated_hours DECIMAL(5,1) DEFAULT 0,
    logged_hours DECIMAL(5,1) DEFAULT 0,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    started_at DATETIME DEFAULT NULL,
    completed_at DATETIME DEFAULT NULL,
    sort_order INT DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

// Migration: add columns if they don't exist (safe for re-runs)
try { $db->exec("ALTER TABLE tasks ADD COLUMN acceptance TEXT DEFAULT NULL AFTER description"); } catch(Exception $e) {}
try { $db->exec("ALTER TABLE tasks ADD COLUMN max_hours DECIMAL(5,1) DEFAULT 0 AFTER deadline"); } catch(Exception $e) {}

$db->exec("CREATE TABLE IF NOT EXISTS task_updates (
    id INT AUTO_INCREMENT PRIMARY KEY,
    task_id INT NOT NULL,
    author VARCHAR(20) NOT NULL,
    message TEXT NOT NULL,
    hours_added DECIMAL(5,1) DEFAULT 0,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

$db->exec("CREATE TABLE IF NOT EXISTS daily_logs (
    id INT AUTO_INCREMENT PRIMARY KEY,
    author VARCHAR(20) NOT NULL,
    log_date DATE NOT NULL,
    done_today TEXT DEFAULT NULL,
    plan_tomorrow TEXT DEFAULT NULL,
    blockers TEXT DEFAULT NULL,
    mood VARCHAR(20) DEFAULT 'neutral',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

$db->exec("CREATE TABLE IF NOT EXISTS messages (
    id INT AUTO_INCREMENT PRIMARY KEY,
    from_user VARCHAR(20) NOT NULL,
    to_user VARCHAR(20) NOT NULL,
    message TEXT NOT NULL,
    is_read TINYINT DEFAULT 0,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

// ========== SEED ==========
$count = (int)$db->query("SELECT COUNT(*) FROM tasks")->fetchColumn();
if ($count === 0) {
    // [owner, sprint, code, title, description, acceptance, priority, status, deadline, max_hours, estimated_hours, sort_order]
    $seed = [
        // ===== DEV MANILLAS =====

        // SPRINT 0: SETUP
        ['jr',0,'0.1','Revisar estructura del proyecto y archivos entregados',
'Se te entrega una carpeta con el proyecto. Lo primero es entender que hay adentro.

ESTRUCTURA DE LA CARPETA QUE RECIBES:
iceberg-pos/
  agent/
    agent.py          -> Servicio HTTP que controla el programador USB (YA FUNCIONA, no tocar por ahora)
    programmer.py     -> Comunicacion directa con el hardware USB/HID (YA FUNCIONA, no tocar)
    config.py         -> Configuracion del agent (puerto 5555)
  server/
    main.py           -> Servidor central FastAPI (NO TOCAR - lo maneja Backend)
    config.py         -> Configuracion del server
    models.py         -> Modelos de base de datos
    schemas.py        -> Schemas de la API
    database.py       -> Conexion a DB
    routers/          -> Endpoints de la API
    services/         -> Logica de negocio
    static/pos.html   -> Interfaz web actual (referencia visual)
  requirements.txt    -> Dependencias Python
  scripts/            -> Scripts de inicio Windows (.bat)

programar_pulsera.py  -> Script CLI para probar el programador (USALO PARA APRENDER)

QUE HACER:
1. Abre CADA archivo de la carpeta agent/ y leelo completo. Entiende que hace cada funcion.
2. Abre programar_pulsera.py y leelo. Este es el que vas a usar para tu primera prueba.
3. Abre server/static/pos.html en un navegador. Esta es la interfaz actual que vas a REEMPLAZAR con Python.
4. Lee requirements.txt para saber que dependencias necesitas.',
'- [ ] Lei y entiendo agent.py (que endpoints tiene, como funciona)
- [ ] Lei y entiendo programmer.py (como se comunica con el USB)
- [ ] Lei programar_pulsera.py (el menu, las opciones)
- [ ] Vi pos.html y entiendo la interfaz actual
- [ ] Se que archivos NO debo tocar (server/)',
'bloqueante','pendiente','',1.5,1.5,1],

        ['jr',0,'0.2','Instalar Python y dependencias',
'PASO A PASO:
1. Descargar Python 3.11+ de python.org (marcar "Add to PATH" al instalar)
2. Abrir terminal/cmd en la carpeta del proyecto
3. Crear entorno virtual:
   python -m venv venv
4. Activar entorno:
   Windows: venv\Scripts\activate
5. Instalar dependencias:
   pip install hidapi flask httpx customtkinter Pillow
6. Verificar instalacion:
   python -c "import hid; import customtkinter; print(\'OK\')"

Si da error con hidapi en Windows, instalar: pip install hidapi
Si sigue fallando, puede necesitar Visual C++ Build Tools.',
'- [ ] Python 3.11+ instalado (python --version muestra 3.11+)
- [ ] Entorno virtual creado y activado
- [ ] Todas las dependencias instaladas sin errores
- [ ] El comando de verificacion imprime OK',
'bloqueante','pendiente','',1,1,2],

        ['jr',0,'0.3','Probar el programador USB con el script CLI',
'OBJETIVO: Programar tu primera pulsera fisicamente.

1. Conecta el programador USB a tu PC
2. Con el venv activado, corre:
   python programar_pulsera.py
3. Selecciona opcion 2 (Programar rapido)
4. Escribe: b (azul)
5. Escribe: 5 (minutos)
6. El programa dira "Esperando pulsera..."
7. ACERCA una pulsera al programador (a 5-10cm)
8. Debe decir "PULSERA #1 PROGRAMADA!"
9. La pulsera debe encender su LED azul
10. Presiona Ctrl+C para salir

SI NO FUNCIONA:
- Verifica que el USB esta bien conectado
- En Windows, revisa Administrador de Dispositivos -> Dispositivos HID
- Debe aparecer un dispositivo con VID 1A86, PID E010
- Si no aparece, necesitas el driver CH9326/WCH',
'- [ ] programar_pulsera.py corre sin errores
- [ ] Puedo programar una pulsera fisicamente (LED enciende)
- [ ] Entiendo el flujo: enviar -> FAIL(sin pulsera) -> acercar -> OK -> LED enciende
- [ ] Probé al menos 2 pulseras diferentes',
'bloqueante','pendiente','',1.5,1,3],

        ['jr',0,'0.4','Levantar sistema completo y hacer una venta',
'OBJETIVO: Ver como funciona el sistema actual de punta a punta.

TERMINAL 1 - Hardware Agent:
  cd iceberg-pos/agent
  python agent.py
  (Debe decir "ICEBERG Hardware Agent" y "Programador USB conectado")

TERMINAL 2 - Servidor:
  cd iceberg-pos/server
  uvicorn main:app --host 0.0.0.0 --port 8000
  (Debe decir "ICEBERG Manillas POS")

NAVEGADOR:
  Abre http://localhost:8000
  - Selecciona "Paquete 30 Min"
  - Click "VENDER"
  - Dice "ACERQUE LA PULSERA..."
  - Acerca una pulsera al programador
  - Debe decir "PROGRAMADA"

SWAGGER (documentacion API):
  Abre http://localhost:8000/docs
  - Aqui ves TODOS los endpoints que vas a usar
  - Prueba GET /api/packages -> te da la lista de paquetes
  - Lee la estructura de POST /api/sell -> esto es lo que tu app va a llamar',
'- [ ] Agent corriendo en puerto 5555
- [ ] Server corriendo en puerto 8000
- [ ] Hice una venta exitosa desde el navegador
- [ ] Explore /docs y entiendo los endpoints
- [ ] Se que datos envia POST /api/sell y que responde',
'bloqueante','pendiente','',2,1.5,4],

        // SPRINT 1: INTERFAZ DE COBRO
        ['jr',1,'1.1','Crear archivo principal de la app y la ventana vacia',
'OBJETIVO: Tener la app de Python abriendo una ventana con tema oscuro.

CREAR EL ARCHIVO: iceberg-pos/app/main.py

Este archivo es el punto de entrada de tu aplicacion.
Al ejecutarlo con "python main.py" debe abrir una ventana de escritorio.

QUE DEBE HACER:
1. Importar customtkinter
2. Configurar tema oscuro:
   customtkinter.set_appearance_mode("dark")
   customtkinter.set_default_color_theme("blue")
3. Crear ventana principal (CTk):
   - Titulo: "ICEBERG POS"
   - Tamanio inicial: 1200x700
   - Color de fondo: #0d1117
4. Dividir en 3 zonas usando frames:
   - HEADER (arriba, altura fija ~50px): donde ira el logo + status
   - CONTENIDO (centro, se expande): donde iran los paquetes y el boton vender
   - FOOTER (abajo, ~200px): donde ira el historial de ventas
5. Ejecutar el mainloop

RESULTADO: Al correr "python app/main.py" se abre una ventana oscura con 3 zonas visibles (puedes poner texto placeholder como "HEADER", "CONTENIDO", "FOOTER" para verificar).',
'- [ ] python app/main.py abre una ventana sin errores
- [ ] Ventana tiene tema oscuro (fondo #0d1117 o similar)
- [ ] Se ven 3 zonas diferenciadas (header, contenido, footer)
- [ ] La ventana se puede maximizar y las zonas se ajustan',
'alta','pendiente','',3,2.5,5],

        ['jr',1,'1.2','Crear el cliente HTTP (api_client.py)',
'OBJETIVO: Un archivo que se encarga de TODA la comunicacion con el servidor.
Tu interfaz NUNCA llama al servidor directo, siempre pasa por este archivo.

CREAR: iceberg-pos/app/api_client.py

CLASE: IcebergAPI
  __init__(self, base_url="http://127.0.0.1:8000")
    - Guarda la URL base
    - Configura timeout de 60 segundos

  get_packages() -> list[dict]
    - Llama GET /api/packages
    - Retorna lista de paquetes
    - Si falla, retorna lista vacia []

  sell(package_id, payment_method, amount, cashier, timeout=30) -> dict
    - Llama POST /api/sell con JSON body
    - !!! ESTA FUNCION BLOQUEA HASTA 30 SEGUNDOS !!!
    - !!! NUNCA LLAMARLA DESDE EL HILO PRINCIPAL DE LA UI !!!
    - Retorna el dict de respuesta
    - Si falla conexion: {"status":"error","message":"Sin conexion"}
    - Si falla timeout: {"status":"timeout","message":"Tiempo agotado"}

  get_sessions(status=None) -> list[dict]
    - Llama GET /api/sessions (opcionalmente con ?status=X)
    - Retorna lista de sesiones de hoy

  get_hardware_status() -> dict
    - Llama GET /api/hardware/status con timeout corto (3seg)
    - Retorna {"agent_reachable":bool, "programmer_connected":bool}

  get_daily_report() -> dict
    - Llama GET /api/reports/daily

  retry_sell(session_id) -> dict
    - Llama POST /api/sell/{session_id}/retry

REGLA: TODAS las funciones deben tener try/except. NUNCA debe crashear.
Si hay error, retorna un valor seguro (lista vacia, dict con status error, etc).',
'- [ ] Archivo creado con la clase IcebergAPI
- [ ] Todas las funciones implementadas (6 funciones)
- [ ] Cada funcion tiene try/except (nunca crash)
- [ ] Probé get_packages() con el servidor corriendo y retorna datos
- [ ] Probé get_packages() con el servidor APAGADO y retorna [] sin crash',
'alta','pendiente','',3,2,6],

        ['jr',1,'1.3','Mostrar los paquetes como botones',
'OBJETIVO: Que al abrir la app, se vean los 4 paquetes disponibles como botones grandes.

EN LA ZONA DE CONTENIDO (izquierda, ~65% del ancho):
- Al iniciar la app, llamar api.get_packages()
- Por cada paquete, crear un boton grande (CTkButton o CTkFrame clickeable)
- Cada boton muestra:
  * Nombre del paquete (ej: "Paquete 60 Min")
  * Minutos (grande, destacado)
  * Precio (ej: "$5.00" en verde)
  * Un circulito del color del LED

COLORES DE REFERENCIA:
  red   -> #f85149 (rojo)
  green -> #3fb950 (verde)
  blue  -> #58a6ff (azul)
  all   -> #bc8cff (morado, para "cumpleanos")

COMPORTAMIENTO:
- Al hacer click en un paquete, se "selecciona" (borde brillante o fondo diferente)
- Solo un paquete puede estar seleccionado a la vez
- Al seleccionar, se auto-llena el precio en el panel de venta

SI LA API NO RESPONDE: Mostrar texto "Sin conexion con el servidor" en vez de botones.',
'- [ ] Se ven 4 botones de paquetes al abrir la app
- [ ] Cada boton muestra nombre, minutos, precio y color
- [ ] Click selecciona (visual claro)
- [ ] Si apago el servidor, muestra "Sin conexion" sin crashear',
'alta','pendiente','',4,3,7],

        ['jr',1,'1.4','Panel de venta (lado derecho) + boton VENDER',
'OBJETIVO: El panel donde se completa la venta y el boton mas importante de la app.

EN LA ZONA DE CONTENIDO (derecha, ~35% del ancho):

PANEL DE VENTA:
- Texto "Paquete seleccionado:" + nombre (o "Ninguno" si no hay)
- Dropdown de metodo de pago: Efectivo / Transferencia / Tarjeta
- Campo numerico "Monto recibido: $" (se llena auto con el precio del paquete)
- BOTON VENDER (grande, todo el ancho del panel, min 60px alto)

ESTADOS DEL BOTON VENDER:
1. GRIS "Seleccione un paquete" -> deshabilitado, no se puede hacer click
2. VERDE "VENDER - $5.00" -> listo, se puede hacer click
3. AMARILLO pulsante "ACERQUE LA PULSERA..." -> esperando (durante la llamada a sell)
4. VERDE BRILLANTE "PROGRAMADA! Paq 60min" -> exito (se muestra 3 segundos, luego resetea)
5. ROJO "ERROR - Toque para reintentar" -> fallo o timeout

!!! CRITICO - THREADING !!!
La funcion api.sell() BLOQUEA hasta 30 segundos.
Si la llamas directo, LA VENTANA SE CONGELA y no responde.

PATRON OBLIGATORIO:
  import threading

  def on_vender_click(self):
      self.btn.configure(text="ACERQUE LA PULSERA...", fg_color="#bb8009", state="disabled")
      thread = threading.Thread(target=self._ejecutar_venta, daemon=True)
      thread.start()

  def _ejecutar_venta(self):
      resultado = self.api.sell(...)  # Esto bloquea hasta 30 seg
      self.after(0, lambda: self._mostrar_resultado(resultado))  # Vuelve al hilo UI

  def _mostrar_resultado(self, resultado):
      if resultado.get("status") == "ok":
          # Mostrar exito...
      else:
          # Mostrar error...',
'- [ ] Panel de venta muestra paquete seleccionado, metodo pago, monto
- [ ] Boton VENDER tiene los 5 estados visuales
- [ ] La venta usa threading (la ventana NO se congela durante 30 seg)
- [ ] Exito: boton verde + texto "PROGRAMADA"
- [ ] Error: boton rojo + opcion de reintentar
- [ ] Probé una venta real con pulsera fisica: funciona',
'critica','pendiente','',5,4,8],

        ['jr',1,'1.5','Historial de ventas del dia (parte inferior)',
'OBJETIVO: Tabla en el footer que muestra las ventas hechas hoy.

EN LA ZONA FOOTER:
- Titulo "Ventas de hoy"
- Tabla/lista con columnas: Hora | Paquete | Monto | Estado
- Datos vienen de api.get_sessions()

COLORES POR ESTADO:
  active     -> texto verde + check ✓
  failed     -> texto rojo + X ✗
  pending    -> texto amarillo + reloj

COMPORTAMIENTO:
- Se carga al iniciar la app
- Se actualiza automaticamente despues de cada venta exitosa
- Si no hay ventas: "Sin ventas registradas hoy"
- Si hay muchas ventas: scroll vertical',
'- [ ] Tabla muestra ventas del dia
- [ ] Colores correctos por estado
- [ ] Se actualiza post-venta sin recargar toda la app
- [ ] Scroll funciona cuando hay muchas ventas
- [ ] Vacio muestra mensaje apropiado',
'alta','pendiente','',3.5,3,9],

        ['jr',1,'1.6','Indicador de hardware + reloj en el header',
'OBJETIVO: Que el operador sepa si el programador USB esta conectado.

EN LA ZONA HEADER:
- Izquierda: texto "ICEBERG POS" (logo)
- Centro o derecha: indicador de hardware
  * Circulo verde + "Programador conectado" si programmer_connected == true
  * Circulo rojo + "Desconectado" si false
- Derecha: reloj en tiempo real (HH:MM)

POLLING:
Cada 5 segundos, llamar api.get_hardware_status()
Usar el metodo .after() de tkinter, NO threading:

  def verificar_hardware(self):
      status = self.api.get_hardware_status()
      # actualizar indicador...
      self.after(5000, self.verificar_hardware)  # repetir en 5 seg',
'- [ ] Se ve "Programador conectado" o "Desconectado"
- [ ] El indicador se actualiza cada 5 seg
- [ ] El reloj muestra hora actual
- [ ] Si desconecto el USB, cambia a rojo en max 5 seg',
'media','pendiente','',2.5,2,10],

        // SPRINT 2: MEJORAS
        ['jr',2,'2.1','Login de cajero al iniciar',
'Al abrir la app, ANTES de mostrar la interfaz de cobro, mostrar una pantalla simple:
- Texto "Nombre del cajero:"
- Campo de texto
- Boton "Entrar"
- Al presionar, guarda el nombre y lo envia en cada venta (campo "cashier" en POST /api/sell)
- Mostrar en el header: "Cajero: Maria"',
'- [ ] Pantalla de login aparece al iniciar
- [ ] No se puede acceder al POS sin poner nombre
- [ ] El nombre se envia en cada venta
- [ ] Se muestra en el header','media','pendiente','',2.5,2,11],

        ['jr',2,'2.2','Configuracion de IP del servidor',
'Boton de engranaje/config en el header. Abre ventana con:
- Campo: "Direccion del servidor" (default: http://127.0.0.1:8000)
- Boton "Probar conexion" -> llama GET /api/hardware/status, muestra si funciona
- Boton "Guardar" -> guarda en archivo config.json en la misma carpeta
- Al iniciar la app, lee config.json para saber la IP',
'- [ ] Puedo cambiar la IP y guardar
- [ ] Se guarda en config.json
- [ ] Al reiniciar la app, usa la IP guardada
- [ ] Boton probar conexion funciona','media','pendiente','',3,2,12],

        ['jr',2,'2.3','Reporte de ventas del dia',
'Boton "Reporte" que abre popup/ventana con:
- Total de ventas del dia (numero)
- Total dinero recaudado (suma)
- Tabla desglose: Paquete | Cantidad | Subtotal
- Datos de GET /api/reports/daily',
'- [ ] Popup muestra resumen correcto
- [ ] Totales coinciden con las ventas hechas
- [ ] Desglose por paquete correcto','media','pendiente','',3,2.5,13],

        ['jr',2,'2.4','Manejo cuando el servidor no responde',
'Si el servidor no responde (no hay conexion):
- Mostrar texto grande "Sin conexion con el servidor" sobre el contenido
- Deshabilitar boton VENDER
- Mostrar boton "Reintentar conexion"
- NUNCA crash, NUNCA pantalla en blanco, NUNCA error de Python en consola',
'- [ ] Si apago el servidor, muestra "Sin conexion"
- [ ] Boton VENDER se deshabilita
- [ ] Boton Reintentar funciona
- [ ] La app nunca crashea','media','pendiente','',3,2,14],

        ['jr',3,'3.1','Script de inicio con doble-click',
'Crear iceberg-pos/iniciar_pos.bat que:
1. Active el venv
2. Inicie el agent.py en segundo plano
3. Espere 2 segundos
4. Inicie la app Python (app/main.py)
El operador de caja solo hace doble-click en este archivo.',
'- [ ] El .bat funciona con doble-click
- [ ] Se inician agent y app juntos
- [ ] No se necesita abrir consola manual','baja','pendiente','',2,1.5,15],

        ['jr',3,'3.2','Pruebas de estabilidad',
'Programar 30+ pulseras seguidas sin parar. Verificar:
- La app no se pone lenta
- No hay errores en consola
- La memoria no crece sin control
- Si desconecto y reconecto el USB, se recupera',
'- [ ] 30+ pulseras programadas seguidas
- [ ] App estable (no lenta, no errores)
- [ ] Documentar resultados en un update aqui','alta','pendiente','',3,3,16],

        // ===== DEV BACKEND =====
        ['lead',1,'L1.1','Server escucha en 0.0.0.0','Cambiar SERVER_HOST a 0.0.0.0. Verificar acceso desde otra PC.',NULL,'alta','pendiente','',1,1,1],
        ['lead',1,'L1.2','CORS habilitado','CORSMiddleware en FastAPI. allow_origins=["*"].',NULL,'alta','pendiente','',1,1,2],
        ['lead',1,'L1.3','Agent Registration endpoint','POST /api/agents/register. Body: {agent_id, host, port}. Auto-registro al arrancar.',NULL,'alta','pendiente','',4,4,3],
        ['lead',1,'L1.4','mDNS Discovery','zeroconf: publicar _iceberg._tcp.local. Agents encuentran server sin IP manual.',NULL,'alta','pendiente','',4,4,4],
        ['lead',2,'L2.1','Modelo Agent en DB','Tabla agents(id,name,host,port,status,last_heartbeat). SQLAlchemy.',NULL,'alta','pendiente','',3,3,5],
        ['lead',2,'L2.2','Heartbeat system','POST /api/agents/{id}/heartbeat cada 30seg. Offline si >60seg.',NULL,'alta','pendiente','',3,3,6],
        ['lead',2,'L2.3','Sell con Agent ID','SellRequest + agent_id. hardware_client busca agent en DB.',NULL,'alta','pendiente','',4,4,7],
        ['lead',3,'L3.1','Cola offline local','Estaciones guardan ventas local si server cae. Sync al reconectar.',NULL,'alta','pendiente','',6,6,8],
        ['lead',3,'L3.2','Sync Queue worker','Procesar sync_queue, enviar al cloud. Backoff exponencial.',NULL,'media','pendiente','',5,5,9],
        ['lead',4,'L4.1','API Keys','Token por agent. Authorization Bearer. Middleware valida.',NULL,'media','pendiente','',4,4,10],
        ['lead',4,'L4.2','VLAN + Red aislada','VLAN POS 192.168.10.0/24. Solo edge server con internet.',NULL,'media','pendiente','',4,4,11],
    ];
    $stmt = $db->prepare("INSERT INTO tasks (owner,sprint,task_code,title,description,acceptance,priority,status,deadline,max_hours,estimated_hours,sort_order) VALUES (?,?,?,?,?,?,?,?,NULLIF(?,''),?,?,?)");
    foreach ($seed as $t) $stmt->execute($t);
}

// ========== API ==========
if (isset($_GET['api'])) {
    header('Content-Type: application/json; charset=utf-8');
    $a = $_GET['api'];

    if ($a === 'tasks') {
        $o = $_GET['owner'] ?? 'jr';
        $s = $db->prepare("SELECT * FROM tasks WHERE owner=? ORDER BY sprint,sort_order"); $s->execute([$o]);
        die(json_encode($s->fetchAll(PDO::FETCH_ASSOC)));
    }

    if ($a === 'task' && isset($_GET['id'])) {
        $s = $db->prepare("SELECT * FROM tasks WHERE id=?"); $s->execute([$_GET['id']]);
        $t = $s->fetch(PDO::FETCH_ASSOC);
        $u = $db->prepare("SELECT * FROM task_updates WHERE task_id=? ORDER BY created_at DESC"); $u->execute([$_GET['id']]);
        $t['updates'] = $u->fetchAll(PDO::FETCH_ASSOC);
        die(json_encode($t));
    }

    if ($a === 'update_task' && $_SERVER['REQUEST_METHOD']==='POST') {
        $d = json_decode(file_get_contents('php://input'), true);
        $allowed = ['status','logged_hours','deadline','priority','title','description','max_hours'];
        $f = $d['field'] ?? ''; $v = $d['value'] ?? ''; $id = (int)($d['id'] ?? 0);
        if (!in_array($f,$allowed) || !$id) die(json_encode(['error'=>'invalid']));
        if ($f === 'deadline' && $v === '') $v = null;
        $db->prepare("UPDATE tasks SET {$f}=? WHERE id=?")->execute([$v,$id]);
        if ($f==='status' && $v==='en_progreso')
            $db->prepare("UPDATE tasks SET started_at=NOW() WHERE id=? AND started_at IS NULL")->execute([$id]);
        if ($f==='status' && $v==='completada')
            $db->prepare("UPDATE tasks SET completed_at=NOW() WHERE id=?")->execute([$id]);
        die(json_encode(['ok'=>true]));
    }

    if ($a === 'add_update' && $_SERVER['REQUEST_METHOD']==='POST') {
        $d = json_decode(file_get_contents('php://input'), true);
        $hours = floatval($d['hours'] ?? 0);
        $db->prepare("INSERT INTO task_updates (task_id,author,message,hours_added) VALUES (?,?,?,?)")
           ->execute([$d['task_id'],$d['author'],$d['message'],$hours]);
        if ($hours > 0)
            $db->prepare("UPDATE tasks SET logged_hours=logged_hours+? WHERE id=?")->execute([$hours,$d['task_id']]);
        die(json_encode(['ok'=>true]));
    }

    if ($a === 'daily_logs') {
        $o = $_GET['author'] ?? 'jr';
        $s = $db->prepare("SELECT * FROM daily_logs WHERE author=? ORDER BY log_date DESC LIMIT 30"); $s->execute([$o]);
        die(json_encode($s->fetchAll(PDO::FETCH_ASSOC)));
    }

    if ($a === 'save_daily' && $_SERVER['REQUEST_METHOD']==='POST') {
        $d = json_decode(file_get_contents('php://input'), true);
        $ex = $db->prepare("SELECT id FROM daily_logs WHERE author=? AND log_date=?");
        $ex->execute([$d['author'],$d['log_date']]);
        $eid = $ex->fetchColumn();
        if ($eid)
            $db->prepare("UPDATE daily_logs SET done_today=?,plan_tomorrow=?,blockers=?,mood=? WHERE id=?")
               ->execute([$d['done'],$d['plan'],$d['blockers']??'',$d['mood']??'neutral',$eid]);
        else
            $db->prepare("INSERT INTO daily_logs (author,log_date,done_today,plan_tomorrow,blockers,mood) VALUES (?,?,?,?,?,?)")
               ->execute([$d['author'],$d['log_date'],$d['done'],$d['plan'],$d['blockers']??'',$d['mood']??'neutral']);
        die(json_encode(['ok'=>true]));
    }

    if ($a === 'messages') {
        $u = $_GET['user'] ?? 'jr';
        $s = $db->prepare("SELECT * FROM messages WHERE to_user=? OR from_user=? ORDER BY created_at DESC LIMIT 50");
        $s->execute([$u,$u]);
        die(json_encode($s->fetchAll(PDO::FETCH_ASSOC)));
    }

    if ($a === 'send_msg' && $_SERVER['REQUEST_METHOD']==='POST') {
        $d = json_decode(file_get_contents('php://input'), true);
        $db->prepare("INSERT INTO messages (from_user,to_user,message) VALUES (?,?,?)")
           ->execute([$d['from'],$d['to'],$d['message']]);
        die(json_encode(['ok'=>true]));
    }

    if ($a === 'mark_read' && $_SERVER['REQUEST_METHOD']==='POST') {
        $d = json_decode(file_get_contents('php://input'), true);
        $db->prepare("UPDATE messages SET is_read=1 WHERE to_user=? AND is_read=0")->execute([$d['user']]);
        die(json_encode(['ok'=>true]));
    }

    if ($a === 'stats') {
        $o = $_GET['owner'] ?? 'jr';
        $r = [];
        foreach (['total'=>"SELECT COUNT(*) FROM tasks WHERE owner=?",'done'=>"SELECT COUNT(*) FROM tasks WHERE owner=? AND status='completada'",'prog'=>"SELECT COUNT(*) FROM tasks WHERE owner=? AND status='en_progreso'",'hest'=>"SELECT COALESCE(SUM(estimated_hours),0) FROM tasks WHERE owner=?",'hlog'=>"SELECT COALESCE(SUM(logged_hours),0) FROM tasks WHERE owner=?",'overdue'=>"SELECT COUNT(*) FROM tasks WHERE owner=? AND status='en_progreso' AND started_at IS NOT NULL AND max_hours>0 AND TIMESTAMPDIFF(MINUTE,started_at,NOW())/60 > max_hours"] as $k=>$q) {
            $s=$db->prepare($q); $s->execute([$o]); $r[$k]=$s->fetchColumn();
        }
        die(json_encode($r));
    }

    if ($a === 'unread') {
        $u = $_GET['user'] ?? 'jr';
        $s=$db->prepare("SELECT COUNT(*) FROM messages WHERE to_user=? AND is_read=0"); $s->execute([$u]);
        die(json_encode(['n'=>(int)$s->fetchColumn()]));
    }

    die(json_encode(['error'=>'unknown']));
}

$view = $_GET['view'] ?? 'home';
?><!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>ICEBERG Manillas<?php if($view!=='home') echo $view==='jr'?' - Dev Manillas':' - Dev Backend'; ?></title>
<style>
:root{--bg0:#0d1117;--bg1:#161b22;--bg2:#1c2333;--bd:#30363d;--t1:#e6edf3;--t2:#8b949e;--t3:#484f58;--blue:#58a6ff;--purple:#bc8cff;--green:#3fb950;--red:#f85149;--yellow:#d29922;--orange:#db6d28}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,'Segoe UI',Helvetica,Arial,sans-serif;background:var(--bg0);color:var(--t1);line-height:1.5}
a{color:var(--blue);text-decoration:none}a:hover{text-decoration:underline}
.nav{background:var(--bg1);border-bottom:1px solid var(--bd);padding:0 1.5rem;display:flex;align-items:center;height:52px;gap:1.5rem;position:sticky;top:0;z-index:100}
.nav .logo{font-weight:800;font-size:1.05rem;background:linear-gradient(90deg,var(--blue),var(--purple));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.nav .links{display:flex;gap:0;flex:1}.nav .links a{padding:.8rem 1rem;color:var(--t2);font-size:.85rem;border-bottom:2px solid transparent;transition:.2s}
.nav .links a:hover{color:var(--t1);text-decoration:none}.nav .links a.on{color:var(--t1);border-color:var(--orange)}
.badge-n{background:var(--red);color:#fff;font-size:.65rem;padding:1px 5px;border-radius:8px;font-weight:700;margin-left:3px;vertical-align:top}
.nav .rt{font-size:.8rem;color:var(--t3)}
.wrap{max-width:1200px;margin:0 auto;padding:1.5rem}
.g2{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem}
.g3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:1rem}
.g4{display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:1rem}
@media(max-width:800px){.g2,.g3,.g4{grid-template-columns:1fr}}
.card{background:var(--bg1);border:1px solid var(--bd);border-radius:12px;overflow:hidden}
.card-h{padding:.7rem 1rem;border-bottom:1px solid var(--bd);font-size:.75rem;text-transform:uppercase;letter-spacing:.8px;color:var(--t2);display:flex;justify-content:space-between;align-items:center}
.card-b{padding:1rem}
.st{background:var(--bg1);border:1px solid var(--bd);border-radius:12px;padding:1.1rem;text-align:center}
.st .v{font-size:1.8rem;font-weight:800}.st .l{font-size:.75rem;color:var(--t2);margin-top:.2rem}
.pbar{background:var(--bd);border-radius:6px;height:8px;overflow:hidden;margin:.4rem 0}
.pfill{height:100%;border-radius:6px;transition:width .6s}

/* TASKS */
.task{display:flex;align-items:flex-start;gap:.7rem;padding:.7rem .8rem;border-bottom:1px solid var(--bd);cursor:pointer;transition:background .12s}
.task:hover{background:var(--bg2)}.task:last-child{border-bottom:none}
.task.overdue{background:rgba(248,81,73,.06);border-left:3px solid var(--red)}
.tcheck{width:20px;height:20px;min-width:20px;border:2px solid var(--bd);border-radius:5px;display:flex;align-items:center;justify-content:center;margin-top:2px;font-size:.7rem;transition:.2s}
.tcheck.done{background:var(--green);border-color:var(--green);color:#fff}
.tcheck.prog{background:var(--yellow);border-color:var(--yellow);color:#000}
.tcheck.block{background:var(--red);border-color:var(--red);color:#fff}
.tinfo{flex:1;min-width:0}
.tinfo .tt{font-size:.88rem;font-weight:600}.tinfo .tt.done{text-decoration:line-through;color:var(--t3)}
.tinfo .tm{font-size:.72rem;color:var(--t2);margin-top:2px;display:flex;gap:.6rem;flex-wrap:wrap;align-items:center}
.tp{font-size:.62rem;padding:2px 7px;border-radius:8px;font-weight:700;text-transform:uppercase}
.tp.critica,.tp.bloqueante{background:rgba(248,81,73,.15);color:var(--red)}
.tp.alta{background:rgba(210,153,34,.15);color:var(--yellow)}
.tp.media{background:rgba(88,166,255,.15);color:var(--blue)}
.tp.baja{background:rgba(139,148,158,.15);color:var(--t2)}
.th{font-size:.72rem;color:var(--t3);white-space:nowrap;text-align:right;min-width:55px;display:flex;flex-direction:column;align-items:flex-end;gap:2px}
.timer{font-size:.7rem;font-weight:700;padding:1px 6px;border-radius:4px;font-variant-numeric:tabular-nums}
.timer.ok{background:rgba(63,185,80,.15);color:var(--green)}
.timer.warn{background:rgba(210,153,34,.2);color:var(--yellow)}
.timer.over{background:rgba(248,81,73,.2);color:var(--red);animation:blink 1s infinite}
@keyframes blink{50%{opacity:.5}}
.sprH{padding:.5rem .8rem;background:var(--bg2);border-bottom:1px solid var(--bd);font-size:.78rem;font-weight:700;color:var(--purple);display:flex;justify-content:space-between}
.ssel{padding:.25rem .5rem;border-radius:5px;font-size:.7rem;font-weight:600;border:none;cursor:pointer;color:#000}
.ssel.pendiente{background:#484f58;color:#fff}.ssel.en_progreso{background:var(--yellow)}
.ssel.en_revision{background:var(--blue)}.ssel.completada{background:var(--green)}
.ssel.bloqueada{background:var(--red);color:#fff}

/* MODAL */
.mover{display:none;position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:200;align-items:center;justify-content:center}
.mover.on{display:flex}
.modal{background:var(--bg1);border:1px solid var(--bd);border-radius:14px;width:92%;max-width:700px;max-height:90vh;overflow-y:auto}
.modal-h{padding:1rem 1.2rem;border-bottom:1px solid var(--bd);display:flex;justify-content:space-between;align-items:center}
.modal-h h3{font-size:1.05rem}
.mx{background:none;border:none;color:var(--t2);font-size:1.4rem;cursor:pointer;padding:0 .2rem}.mx:hover{color:var(--t1)}
.modal-b{padding:1.2rem}
.accept-list{background:var(--bg0);border:1px solid var(--bd);border-radius:8px;padding:.8rem;margin:.6rem 0;font-size:.82rem;color:var(--t2);white-space:pre-line;line-height:1.7}
.accept-list b{color:var(--green)}

/* FORMS */
.fg{margin-bottom:.9rem}.fg label{display:block;font-size:.78rem;color:var(--t2);margin-bottom:.2rem}
.fg input,.fg textarea,.fg select{width:100%;padding:.5rem .7rem;background:var(--bg0);border:1px solid var(--bd);border-radius:7px;color:var(--t1);font-size:.85rem;font-family:inherit}
.fg textarea{resize:vertical;min-height:55px}
.fg input:focus,.fg textarea:focus,.fg select:focus{outline:none;border-color:var(--blue)}
.btn{padding:.5rem 1.1rem;border:none;border-radius:7px;cursor:pointer;font-size:.82rem;font-weight:600;transition:.2s;display:inline-flex;align-items:center;gap:.3rem}
.btn-p{background:var(--blue);color:#000}.btn-p:hover{filter:brightness(1.15)}
.btn-g{background:var(--green);color:#000}.btn-d{background:var(--red);color:#fff}
.btn-s{background:var(--bd);color:var(--t1)}.btn-sm{padding:.3rem .7rem;font-size:.72rem}

.tabs{display:flex;gap:0;border-bottom:1px solid var(--bd);margin-bottom:1rem}
.tab{padding:.6rem 1.1rem;background:none;border:none;border-bottom:2px solid transparent;color:var(--t2);cursor:pointer;font-size:.82rem;transition:.2s}
.tab:hover{color:var(--t1)}.tab.on{color:var(--blue);border-color:var(--blue)}
.tc{display:none}.tc.on{display:block}

.dentry{padding:.9rem;border-bottom:1px solid var(--bd)}.dentry:last-child{border-bottom:none}
.ddate{font-weight:700;color:var(--blue);margin-bottom:.4rem;display:flex;align-items:center;gap:.4rem}
.dsec{margin:.3rem 0}.dsec b{color:var(--green);font-size:.78rem}
.dsec p{font-size:.82rem;color:var(--t2);margin-left:.4rem;white-space:pre-line}
.msg{padding:.6rem .8rem;border-bottom:1px solid var(--bd)}
.msg.unread{background:rgba(88,166,255,.05);border-left:3px solid var(--blue)}
.msg-f{font-weight:700;font-size:.78rem;color:var(--purple)}.msg-t{font-size:.82rem;margin:.15rem 0}.msg-d{font-size:.68rem;color:var(--t3)}
.upd{padding:.5rem 0;border-bottom:1px solid rgba(48,54,61,.5);font-size:.82rem}
.upd:last-child{border-bottom:none}.upd-m{font-size:.68rem;color:var(--t3)}

.hero{text-align:center;padding:3rem 1rem 2rem;background:linear-gradient(180deg,var(--bg1),var(--bg0))}
.hero h1{font-size:2.3rem;font-weight:800;background:linear-gradient(90deg,var(--blue),var(--purple),var(--orange));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.hero p{color:var(--t2);font-size:1rem;margin-top:.4rem}
.phase-b{display:inline-block;margin-top:.8rem;padding:.35rem 1rem;background:rgba(219,109,40,.15);border:1px solid var(--orange);border-radius:20px;color:var(--orange);font-weight:700;font-size:.8rem}
.tcard{background:var(--bg1);border:1px solid var(--bd);border-radius:12px;padding:1.3rem;text-align:center;transition:.2s;display:block}
.tcard:hover{border-color:var(--blue);transform:translateY(-2px);text-decoration:none}
.tcard .role{font-size:.7rem;text-transform:uppercase;letter-spacing:1px;color:var(--t3)}
.tcard .name{font-size:1.15rem;font-weight:700;color:var(--t1);margin:.4rem 0}
.tcard .desc{font-size:.8rem;color:var(--t2)}
.tcard .av{font-size:2.5rem;margin-bottom:.4rem}
.arch{font-family:'Cascadia Code','Fira Code',monospace;font-size:.7rem;background:var(--bg0);padding:.8rem;border-radius:8px;overflow-x:auto;line-height:1.35;color:var(--green);white-space:pre}
.empty{padding:1.5rem;text-align:center;color:var(--t3);font-size:.85rem}
.flash{position:fixed;top:62px;right:1.2rem;z-index:300;padding:.6rem 1rem;background:var(--green);color:#000;border-radius:8px;font-weight:600;font-size:.85rem;animation:si .3s,fo .4s 2.5s forwards}
@keyframes si{from{transform:translateX(80px);opacity:0}to{transform:translateX(0);opacity:1}}
@keyframes fo{from{opacity:1}to{opacity:0}}
::-webkit-scrollbar{width:5px}::-webkit-scrollbar-track{background:var(--bg0)}::-webkit-scrollbar-thumb{background:var(--bd);border-radius:3px}
</style>
</head>
<body>

<nav class="nav">
    <a href="?" class="logo" style="text-decoration:none">ICEBERG</a>
    <div class="links">
        <a href="?" class="<?=$view==='home'?'on':''?>">Proyecto</a>
        <a href="?view=jr" class="<?=$view==='jr'?'on':''?>">Dev Manillas<span id="jb" class="badge-n" style="display:none"></span></a>
        <a href="?view=lead" class="<?=$view==='lead'?'on':''?>">Dev Backend<span id="lb" class="badge-n" style="display:none"></span></a>
    </div>
    <div class="rt" id="ck"></div>
</nav>

<?php if($view==='home'): ?>
<div class="hero">
    <h1>ICEBERG Manillas POS</h1>
    <p>Sistema de programacion de pulseras LED para parques de entretenimiento</p>
    <div class="phase-b">Fase 3: Wristbands IR Integrado</div>
</div>
<div class="wrap">
    <div class="g3" style="margin-bottom:1.2rem">
        <div class="st"><div class="v" style="color:var(--blue)" id="ht">--</div><div class="l">Tareas totales</div></div>
        <div class="st"><div class="v" style="color:var(--green)" id="hd">--</div><div class="l">Completadas</div></div>
        <div class="st"><div class="v" style="color:var(--yellow)" id="hp">--%</div><div class="l">Progreso general</div></div>
    </div>
    <div class="pbar" style="height:10px;margin-bottom:1.5rem"><div class="pfill" id="hbar" style="width:0%;background:linear-gradient(90deg,var(--blue),var(--green))"></div></div>
    <div class="g2" style="margin-bottom:1.5rem">
        <a href="?view=lead" class="tcard">
            <div class="av">&#128736;</div>
            <div class="role">Dev Backend</div>
            <div class="name">Red Privada & Servidor</div>
            <div class="desc">FastAPI, base de datos, red LAN privada, sincronizacion cloud, seguridad</div>
            <div style="margin-top:.8rem"><div class="pbar"><div class="pfill" id="lpb" style="width:0%;background:var(--purple)"></div></div><span style="font-size:.7rem;color:var(--t3)" id="lpp">0%</span></div>
        </a>
        <a href="?view=jr" class="tcard">
            <div class="av">&#9889;</div>
            <div class="role">Dev Manillas</div>
            <div class="name">Interfaz de Cobro + Hardware</div>
            <div class="desc">App Python de escritorio para cobrar y programar pulseras LED</div>
            <div style="margin-top:.8rem"><div class="pbar"><div class="pfill" id="jpb" style="width:0%;background:var(--orange)"></div></div><span style="font-size:.7rem;color:var(--t3)" id="jpp">0%</span></div>
        </a>
    </div>
    <div class="card" style="margin-bottom:1.5rem"><div class="card-h">Arquitectura</div><div class="card-b"><div class="arch">         EDGE SERVER (Mini PC)              ESTACION POS (Tablet/PC)
        ┌────────────────────┐            ┌──────────────────────┐
        │  FastAPI :8000     │   HTTP     │  App Python (UI)     │
        │  SQLite DB         │◄──────────►│  + Hardware Agent    │
        │  mDNS Discovery    │   LAN      │  + Programador USB   │
        └────────────────────┘            └──────────┬───────────┘
                                                     │ IR
                                                ┌────┴────┐
                                                │ PULSERA  │
                                                └─────────┘</div></div></div>
    <div class="card" style="margin-bottom:1.5rem"><div class="card-h">Archivos del Proyecto</div><div class="card-b">
        <p style="font-size:.88rem;margin-bottom:.8rem;color:var(--t2)">Descarga los archivos del proyecto para empezar a trabajar. Incluye todo el codigo fuente, scripts de inicio, y el contexto para Claude Chat.</p>
        <div style="display:flex;gap:1rem;flex-wrap:wrap;margin-bottom:1rem">
            <a href="download.php" class="btn btn-g" style="text-decoration:none;font-size:.9rem;padding:.7rem 1.5rem">&#128230; Descargar Proyecto (.zip)</a>
            <a href="CONTEXTO_CLAUDE.md" target="_blank" class="btn btn-p" style="text-decoration:none;font-size:.9rem;padding:.7rem 1.5rem">&#129302; Contexto para Claude Chat</a>
        </div>
        <div class="arch" style="font-size:.68rem;max-height:280px;overflow-y:auto">iceberg-pos/
├── programar_pulsera.py            # Script CLI para probar programador (EMPEZAR AQUI)
├── requirements.txt                # Dependencias Python
│
├── agent/                          # Hardware Agent (YA FUNCIONA)
│   ├── agent.py                    # Flask service: /status, /program
│   ├── programmer.py               # USB HID directo con CH9326
│   └── config.py                   # PORT=5555
│
├── server/                         # Servidor Central (NO TOCAR)
│   ├── main.py                     # FastAPI app
│   ├── config.py                   # Configuracion
│   ├── database.py                 # SQLAlchemy
│   ├── models.py                   # Package, Session, AuditLog
│   ├── schemas.py                  # Pydantic schemas
│   ├── routers/
│   │   ├── pos.py                  # /api/packages, /api/sell
│   │   ├── sessions.py             # /api/sessions, /api/reports
│   │   └── hardware.py             # /api/hardware/status
│   ├── services/
│   │   ├── hardware_client.py      # HTTP client al Agent
│   │   └── wristband_service.py    # Logica de venta
│   ├── static/
│   │   └── pos.html                # Interfaz web actual (REFERENCIA)
│   └── __init__.py
│
├── scripts/
│   ├── install.bat                 # Instalar dependencias
│   ├── start_agent.bat             # Iniciar agent
│   └── start_server.bat            # Iniciar server
│
├── CONTEXTO_CLAUDE.md              # Darle esto a Claude Chat para contexto
│
└── app/                            # &lt;&lt;&lt; TU CARPETA - CREAR AQUI &gt;&gt;&gt;
    ├── main.py                     # Punto de entrada
    ├── api.py                      # Cliente HTTP al server
    └── ui/                         # Ventanas customtkinter
        ├── main_window.py
        ├── package_selector.py
        ├── sale_panel.py
        └── ...</div>
    </div></div>
    <div class="card"><div class="card-h">Actividad reciente</div><div id="hact" class="card-b"><div class="empty">Cargando...</div></div></div>
</div>

<?php elseif($view==='jr' || $view==='lead'): ?>
<?php
$isJr=$view==='jr'; $owner=$isJr?'jr':'lead'; $other=$isJr?'lead':'jr';
$rn=$isJr?'Desarrollador Funcional - Interfaz de Cobro':'Desarrollador Backend - Red Privada & Servidor';
$rd=$isJr?'Objetivo: Crear la app de escritorio en Python para cobrar y programar pulseras LED':'Objetivo: Sistema de red privada local, servidor central, sincronizacion';
?>
<div class="wrap">
    <div style="margin-bottom:1.2rem">
        <h2 style="font-size:1.2rem"><?=$rn?></h2>
        <p style="font-size:.82rem;color:var(--orange);font-weight:600"><?=$rd?></p>
    </div>
    <div class="g4" style="margin-bottom:.5rem">
        <div class="st"><div class="v" style="color:var(--green)" id="sd">--</div><div class="l">Completadas</div></div>
        <div class="st"><div class="v" style="color:var(--yellow)" id="sp">--</div><div class="l">En progreso</div></div>
        <div class="st"><div class="v" style="color:var(--blue)" id="sh">--</div><div class="l">Horas log/est</div></div>
        <div class="st"><div class="v" style="color:var(--red)" id="sov">--</div><div class="l">Fuera de tiempo</div></div>
    </div>
    <div class="pbar" style="height:9px;margin-bottom:1.2rem"><div class="pfill" id="dpb" style="width:0%;background:linear-gradient(90deg,var(--green),var(--blue))"></div></div>

    <div class="tabs">
        <button class="tab on" onclick="stab('tasks',this)">Tareas</button>
        <button class="tab" onclick="stab('daily',this)">Daily Log</button>
        <button class="tab" onclick="stab('msgs',this)">Mensajes <span id="mb" class="badge-n" style="display:none"></span></button>
    </div>

    <div class="tc on" id="t-tasks"><div class="card"><div id="tlist"></div></div></div>

    <div class="tc" id="t-daily">
        <div class="card" style="margin-bottom:1rem"><div class="card-h">Registro de hoy - <?=date('d/m/Y')?></div><div class="card-b">
            <div class="fg"><label>Que hice hoy?</label><textarea id="dd" rows="3" placeholder="- Termine la tarea 1.3&#10;- Avance en el grid de paquetes"></textarea></div>
            <div class="fg"><label>Que voy a hacer manana?</label><textarea id="dp" rows="3" placeholder="- Empezar tarea 1.4&#10;- Probar flujo de venta"></textarea></div>
            <div class="fg"><label>Bloqueos / Problemas</label><textarea id="db" rows="2" placeholder="Ninguno / No tengo el programador USB..."></textarea></div>
            <div class="fg"><label>Como te sientes?</label>
                <div style="display:flex;gap:1rem;margin-top:.2rem">
                    <span class="mo" data-m="bad" style="cursor:pointer;font-size:1.4rem;opacity:.4">&#128546;</span>
                    <span class="mo" data-m="neutral" style="cursor:pointer;font-size:1.4rem;opacity:.4">&#128528;</span>
                    <span class="mo" data-m="good" style="cursor:pointer;font-size:1.4rem;opacity:.4">&#128513;</span>
                    <span class="mo" data-m="great" style="cursor:pointer;font-size:1.4rem;opacity:.4">&#128640;</span>
                </div>
            </div>
            <button class="btn btn-g" onclick="saveDaily()">Guardar registro</button>
        </div></div>
        <div class="card"><div class="card-h">Historial</div><div id="dhist"></div></div>
    </div>

    <div class="tc" id="t-msgs">
        <div class="card" style="margin-bottom:1rem"><div class="card-b">
            <div class="fg" style="margin-bottom:.4rem"><textarea id="mi" rows="2" placeholder="Mensaje para <?=$isJr?'Backend':'Manillas'?>..."></textarea></div>
            <button class="btn btn-p btn-sm" onclick="sendMsg()">Enviar</button>
        </div></div>
        <div class="card"><div class="card-h">Conversacion</div><div id="mlist"></div></div>
    </div>
</div>

<div class="mover" id="tmod">
    <div class="modal">
        <div class="modal-h"><h3 id="mtitle">Tarea</h3><button class="mx" onclick="cmod()">&times;</button></div>
        <div class="modal-b" id="mbody"></div>
    </div>
</div>
<?php endif; ?>

<script>
const V='<?=$view?>',OW='<?=$owner??""?>',OT='<?=$other??""?>';
const MO={bad:'\u{1F622}',neutral:'\u{1F610}',good:'\u{1F601}',great:'\u{1F680}'};
let mood='neutral',_taskCache=[];

setInterval(()=>{const n=new Date();const e=document.getElementById('ck');if(e)e.textContent=n.toLocaleDateString('es-EC',{day:'2-digit',month:'short'})+' '+n.toLocaleTimeString('es-EC',{hour:'2-digit',minute:'2-digit'})},1000);

async function F(a,p={},m='GET',b=null){let u='?api='+a;for(let[k,v] of Object.entries(p))u+='&'+k+'='+encodeURIComponent(v);const o={method:m,headers:{'Content-Type':'application/json'}};if(b)o.body=JSON.stringify(b);return(await fetch(u,o)).json()}
function flash(m,c){const e=document.createElement('div');e.className='flash';if(c)e.style.background=c;e.textContent=m;document.body.appendChild(e);setTimeout(()=>e.remove(),3200)}
function esc(s){if(!s)return'';const d=document.createElement('div');d.textContent=s;return d.innerHTML}

// Timer helpers
function getElapsedHours(startedAt){
    if(!startedAt)return 0;
    const s=new Date(startedAt.replace(' ','T'));
    return(Date.now()-s.getTime())/3600000;
}
function fmtTimer(hoursLeft){
    if(hoursLeft<=0)return{text:'EXCEDIDO',cls:'over'};
    const h=Math.floor(hoursLeft);
    const m=Math.floor((hoursLeft-h)*60);
    if(hoursLeft<0.5)return{text:`${m}m`,cls:'warn'};
    return{text:`${h}h${m>0?m+'m':''}`,cls:h<1?'warn':'ok'};
}

// === HOME ===
async function homeInit(){
    const[jr,ld]=await Promise.all([F('stats',{owner:'jr'}),F('stats',{owner:'lead'})]);
    const tt=+jr.total+(+ld.total),td=+jr.done+(+ld.done);
    const pct=tt?Math.round(td/tt*100):0;
    document.getElementById('ht').textContent=tt;
    document.getElementById('hd').textContent=td;
    document.getElementById('hp').textContent=pct+'%';
    document.getElementById('hbar').style.width=pct+'%';
    const jp=jr.total?Math.round(jr.done/jr.total*100):0;
    const lp=ld.total?Math.round(ld.done/ld.total*100):0;
    document.getElementById('jpb').style.width=jp+'%';document.getElementById('jpp').textContent=jp+'% ('+jr.done+'/'+jr.total+')';
    document.getElementById('lpb').style.width=lp+'%';document.getElementById('lpp').textContent=lp+'% ('+ld.done+'/'+ld.total+')';
    const[jlogs,llogs]=await Promise.all([F('daily_logs',{author:'jr'}),F('daily_logs',{author:'lead'})]);
    const all=[...jlogs.map(l=>({...l,who:'Manillas'})),...llogs.map(l=>({...l,who:'Backend'}))].sort((a,b)=>b.log_date.localeCompare(a.log_date)).slice(0,5);
    const c=document.getElementById('hact');
    if(!all.length){c.innerHTML='<div class="empty">Sin actividad registrada aun</div>';return}
    let h='';
    for(const l of all){
        const mi=MO[l.mood]||MO.neutral;
        h+=`<div class="dentry"><div class="ddate">${mi} <b>${l.who}</b> - ${l.log_date}</div>`;
        if(l.done_today)h+=`<div class="dsec"><b>Hecho:</b><p>${esc(l.done_today)}</p></div>`;
        if(l.plan_tomorrow)h+=`<div class="dsec"><b>Plan:</b><p>${esc(l.plan_tomorrow)}</p></div>`;
        if(l.blockers)h+=`<div class="dsec"><b style="color:var(--red)">Bloqueo:</b><p>${esc(l.blockers)}</p></div>`;
        h+='</div>';
    }
    c.innerHTML=h;
    const[ju,lu]=await Promise.all([F('unread',{user:'jr'}),F('unread',{user:'lead'})]);
    if(ju.n>0){const e=document.getElementById('jb');if(e){e.textContent=ju.n;e.style.display='inline'}}
    if(lu.n>0){const e=document.getElementById('lb');if(e){e.textContent=lu.n;e.style.display='inline'}}
}

// === DEV ===
async function devStats(){
    const s=await F('stats',{owner:OW});
    const pct=s.total?Math.round(s.done/s.total*100):0;
    document.getElementById('sd').textContent=s.done+'/'+s.total;
    document.getElementById('sp').textContent=s.prog;
    document.getElementById('sh').textContent=(+s.hlog).toFixed(1)+'/'+(+s.hest)+'h';
    document.getElementById('sov').textContent=s.overdue||'0';
    document.getElementById('dpb').style.width=pct+'%';
}

async function loadTasks(){
    const tasks=await F('tasks',{owner:OW});
    _taskCache=tasks;
    renderTasks();
}

function renderTasks(){
    const tasks=_taskCache;
    const c=document.getElementById('tlist');
    if(!tasks.length){c.innerHTML='<div class="empty">No hay tareas</div>';return}
    let h='',cs=-1;
    const sprintNames={0:'Setup inicial',1:'Sprint 1: Core',2:'Sprint 2: Mejoras',3:'Sprint 3: Produccion',4:'Sprint 4: Red'};
    for(const t of tasks){
        if(t.sprint!==cs){
            cs=t.sprint;
            const st=tasks.filter(x=>x.sprint==cs),sd=st.filter(x=>x.status==='completada').length;
            h+=`<div class="sprH"><span>${sprintNames[cs]||'Sprint '+cs}</span><span>${sd}/${st.length}</span></div>`;
        }
        const dn=t.status==='completada',pr=t.status==='en_progreso',bl=t.status==='bloqueada';
        const cc=dn?'done':pr?'prog':bl?'block':'';
        const ci=dn?'&#10003;':pr?'&#9654;':bl?'&#9888;':'';

        // Timer
        let timerHtml='';
        let isOverdue=false;
        if(pr && t.max_hours>0 && t.started_at){
            const elapsed=getElapsedHours(t.started_at);
            const left=t.max_hours-elapsed;
            const ti=fmtTimer(left);
            timerHtml=`<span class="timer ${ti.cls}">${ti.text}</span>`;
            if(left<=0)isOverdue=true;
        }else if(!dn && t.max_hours>0){
            timerHtml=`<span style="font-size:.68rem;color:var(--t3)">Max ${t.max_hours}h</span>`;
        }

        const hr=t.logged_hours>0?`${(+t.logged_hours).toFixed(1)}/${t.estimated_hours}h`:`${t.estimated_hours}h`;
        h+=`<div class="task${isOverdue?' overdue':''}" onclick="oTask(${t.id})">
            <div class="tcheck ${cc}">${ci}</div>
            <div class="tinfo">
                <div class="tt ${dn?'done':''}">${esc(t.task_code)} - ${esc(t.title)}</div>
                <div class="tm">
                    <span class="tp ${t.priority}">${t.priority}</span>
                    <select class="ssel ${t.status.replace(/ /g,'_')}" onclick="event.stopPropagation()" onchange="chSt(${t.id},this.value)">
                        <option value="pendiente"${t.status==='pendiente'?' selected':''}>Pendiente</option>
                        <option value="en_progreso"${t.status==='en_progreso'?' selected':''}>En progreso</option>
                        <option value="en_revision"${t.status==='en_revision'?' selected':''}>En revision</option>
                        <option value="bloqueada"${t.status==='bloqueada'?' selected':''}>Bloqueada</option>
                        <option value="completada"${t.status==='completada'?' selected':''}>Completada</option>
                    </select>
                </div>
            </div>
            <div class="th">${timerHtml}<span>${hr}</span></div>
        </div>`;
    }
    c.innerHTML=h;
}

// Update timers every 30 seconds
setInterval(()=>{if(_taskCache.length)renderTasks()},30000);

async function chSt(id,v){
    await F('update_task',{},'POST',{id,field:'status',value:v});
    flash(v==='completada'?'Tarea completada!':'Estado actualizado');
    loadTasks();devStats();
}

async function oTask(id){
    const t=await F('task',{id});if(!t||t.error)return;
    document.getElementById('mtitle').textContent=t.task_code+' - '+t.title;
    const dn=t.status==='completada';

    // Timer in modal
    let timerBlock='';
    if(t.status==='en_progreso' && t.max_hours>0 && t.started_at){
        const elapsed=getElapsedHours(t.started_at);
        const left=t.max_hours-elapsed;
        const ti=fmtTimer(left);
        timerBlock=`<div style="background:var(--bg0);border:1px solid var(--bd);border-radius:8px;padding:.8rem;margin:.6rem 0;text-align:center">
            <div style="font-size:.72rem;color:var(--t2)">TIEMPO RESTANTE</div>
            <div style="font-size:1.6rem;font-weight:800" class="timer ${ti.cls}">${ti.text}</div>
            <div style="font-size:.7rem;color:var(--t3)">de ${t.max_hours}h max | ${elapsed.toFixed(1)}h transcurridas</div>
        </div>`;
    }else if(!dn && t.max_hours>0){
        timerBlock=`<div style="font-size:.78rem;color:var(--t3);margin:.4rem 0">Tiempo maximo: <b>${t.max_hours}h</b> (inicia al cambiar a "En progreso")</div>`;
    }

    let uh='';
    if(t.updates&&t.updates.length)for(const u of t.updates)uh+=`<div class="upd"><div><b>${esc(u.author)}:</b> ${esc(u.message)}${u.hours_added>0?' <span style="color:var(--yellow)">(+'+u.hours_added+'h)</span>':''}</div><div class="upd-m">${u.created_at}</div></div>`;
    else uh='<div class="empty">Sin actualizaciones aun</div>';

    const accHtml=t.acceptance?`<div style="margin:.6rem 0"><div style="font-size:.78rem;color:var(--green);font-weight:700;margin-bottom:.3rem">CRITERIOS DE ACEPTACION:</div><div class="accept-list">${esc(t.acceptance).replace(/- \[ \]/g,'<span style="color:var(--t3)">&#9744;</span>').replace(/- \[x\]/g,'<span style="color:var(--green)">&#9745;</span>')}</div></div>`:'';

    document.getElementById('mbody').innerHTML=`
        <div style="margin-bottom:.6rem">
            <span class="tp ${t.priority}">${t.priority}</span>
            <span class="ssel ${t.status.replace(/ /g,'_')}" style="margin-left:.4rem;display:inline-block;cursor:default">${t.status}</span>
            <span style="margin-left:.5rem;font-size:.78rem;color:var(--t3)">&#9201; ${(+t.logged_hours).toFixed(1)}/${t.estimated_hours}h</span>
        </div>
        ${timerBlock}
        <div style="background:var(--bg0);padding:.8rem;border-radius:7px;font-size:.82rem;color:var(--t2);margin-bottom:.6rem;white-space:pre-line;line-height:1.6">${esc(t.description)}</div>
        ${accHtml}
        ${t.started_at?'<div style="font-size:.72rem;color:var(--t3)">Iniciada: '+t.started_at+'</div>':''}
        ${t.completed_at?'<div style="font-size:.72rem;color:var(--green)">Completada: '+t.completed_at+'</div>':''}
        <div style="border-top:1px solid var(--bd);margin:.8rem 0;padding-top:.8rem">
            <h4 style="font-size:.82rem;margin-bottom:.6rem">Reportar avance</h4>
            <div class="fg"><textarea id="umsg" rows="2" placeholder="Que avanzaste en esta tarea?"></textarea></div>
            <div style="display:flex;gap:.8rem;align-items:center;flex-wrap:wrap">
                <div class="fg" style="margin:0;width:100px"><input type="number" id="uhr" placeholder="Horas" step="0.5" min="0"></div>
                <button class="btn btn-p btn-sm" onclick="addUpd(${t.id})">Guardar</button>
            </div>
        </div>
        <div style="border-top:1px solid var(--bd);margin:.8rem 0;padding-top:.8rem">
            <h4 style="font-size:.82rem;margin-bottom:.4rem">Historial de avances</h4>${uh}
        </div>`;
    document.getElementById('tmod').classList.add('on');
}
function cmod(){document.getElementById('tmod').classList.remove('on')}
document.getElementById('tmod')?.addEventListener('click',function(e){if(e.target===this)cmod()});

async function addUpd(tid){
    const msg=document.getElementById('umsg').value.trim();if(!msg)return;
    const hr=parseFloat(document.getElementById('uhr').value)||0;
    await F('add_update',{},'POST',{task_id:tid,author:OW,message:msg,hours:hr});
    flash('Avance guardado');oTask(tid);loadTasks();devStats();
}
async function uf(tid,f,v){await F('update_task',{},'POST',{id:tid,field:f,value:v});flash('Actualizado');loadTasks()}

function stab(t,btn){
    document.querySelectorAll('.tab').forEach(b=>b.classList.remove('on'));
    document.querySelectorAll('.tc').forEach(c=>c.classList.remove('on'));
    document.getElementById('t-'+t).classList.add('on');btn.classList.add('on');
    if(t==='daily')loadDaily();if(t==='msgs')loadMsgs();
}

document.querySelectorAll('.mo').forEach(e=>e.addEventListener('click',()=>{
    document.querySelectorAll('.mo').forEach(m=>m.style.opacity='.4');
    e.style.opacity='1';mood=e.dataset.m;
}));

async function saveDaily(){
    const d=document.getElementById('dd').value.trim(),p=document.getElementById('dp').value.trim(),b=document.getElementById('db').value.trim();
    if(!d&&!p){alert('Escribe al menos que hiciste o que vas a hacer');return}
    await F('save_daily',{},'POST',{author:OW,log_date:new Date().toISOString().split('T')[0],done:d,plan:p,blockers:b,mood});
    flash('Daily guardado!');loadDaily();
}

async function loadDaily(){
    const logs=await F('daily_logs',{author:OW});
    const c=document.getElementById('dhist');
    if(!logs.length){c.innerHTML='<div class="empty">Sin registros</div>';return}
    let h='';
    const MOH={bad:'&#128546;',neutral:'&#128528;',good:'&#128513;',great:'&#128640;'};
    for(const l of logs){
        const d=new Date(l.log_date+'T12:00:00');
        const dn=d.toLocaleDateString('es-EC',{weekday:'long',day:'numeric',month:'short'});
        h+=`<div class="dentry"><div class="ddate">${MOH[l.mood]||MOH.neutral} ${dn}</div>`;
        if(l.done_today)h+=`<div class="dsec"><b>&#10003; Hecho:</b><p>${esc(l.done_today)}</p></div>`;
        if(l.plan_tomorrow)h+=`<div class="dsec"><b>&#9654; Plan:</b><p>${esc(l.plan_tomorrow)}</p></div>`;
        if(l.blockers)h+=`<div class="dsec"><b style="color:var(--red)">&#9888; Bloqueo:</b><p>${esc(l.blockers)}</p></div>`;
        h+='</div>';
    }
    c.innerHTML=h;
    const td=new Date().toISOString().split('T')[0],tl=logs.find(l=>l.log_date===td);
    if(tl){
        document.getElementById('dd').value=tl.done_today||'';
        document.getElementById('dp').value=tl.plan_tomorrow||'';
        document.getElementById('db').value=tl.blockers||'';
        mood=tl.mood||'neutral';
        document.querySelectorAll('.mo').forEach(m=>m.style.opacity=m.dataset.m===mood?'1':'.4');
    }
}

async function loadMsgs(){
    const msgs=await F('messages',{user:OW});
    const c=document.getElementById('mlist');
    if(!msgs.length){c.innerHTML='<div class="empty">Sin mensajes</div>';return}
    let h='';
    for(const m of msgs){
        const un=m.to_user===OW&&!m.is_read;
        const fl=m.from_user===OW?'Tu':(m.from_user==='jr'?'Manillas':'Backend');
        h+=`<div class="msg${un?' unread':''}"><div class="msg-f">${fl}</div><div class="msg-t">${esc(m.message)}</div><div class="msg-d">${m.created_at}</div></div>`;
    }
    c.innerHTML=h;
    await F('mark_read',{},'POST',{user:OW});chkUn();
}

async function sendMsg(){
    const m=document.getElementById('mi').value.trim();if(!m)return;
    await F('send_msg',{},'POST',{from:OW,to:OT,message:m});
    document.getElementById('mi').value='';flash('Enviado');loadMsgs();
}

async function chkUn(){
    if(!OW)return;
    const d=await F('unread',{user:OW});
    const b=document.getElementById('mb');
    if(b){if(d.n>0){b.textContent=d.n;b.style.display='inline'}else b.style.display='none'}
}

async function navBadges(){
    const[ju,lu]=await Promise.all([F('unread',{user:'jr'}),F('unread',{user:'lead'})]);
    const jb=document.getElementById('jb'),lb=document.getElementById('lb');
    if(jb){if(ju.n>0){jb.textContent=ju.n;jb.style.display='inline'}else jb.style.display='none'}
    if(lb){if(lu.n>0){lb.textContent=lu.n;lb.style.display='inline'}else lb.style.display='none'}
}

document.addEventListener('DOMContentLoaded',()=>{
    navBadges();
    if(V==='home')homeInit();
    else if(V==='jr'||V==='lead'){devStats();loadTasks();chkUn()}
});
</script>
</body>
</html>