# ICEBERG UI — Contexto
#ui #customtkinter #dialogs #views #sale-flow #overlay #stock #supervisor-pin

## Proposito
Todos los componentes visuales de la app POS. Dark mode deep navy con `customtkinter`. Paleta: fondo `#060b18`, header `#0a1628`, cards `#0f1d32`, acento `#00d4ff`.

## Archivos del modulo `app/ui/`
| Archivo | Componente | Descripcion |
|---------|-----------|-------------|
| `main_window.py` | `MainWindow(CTkFrame)` | Panel raiz — header + sistema de vistas (sin tabs) |
| `login_screen.py` | `LoginScreen(CTkFrame)` | Pantalla de login con USB/server checks |
| `package_selector.py` | `PackageSelector` | Grid de combos artisticos + toolbar 4 botones + overlay offline |
| `sale_dialog.py` | `SaleDialog` | Modal de venta — threading + estados |
| `active_wristbands_tab.py` | `ActiveWristbandsTab` | Tabla pulseras activas + barras progreso + stats |
| `history_panel.py` | `HistoryPanel` | Tabla de sesiones del dia |
| `status_bar.py` | funciones | Polling hardware cada 5s → `set_server_status()` |
| `config_dialog.py` | `ConfigDialog` | Editar server_url, agent_url, stock, sede, agent_id |
| `report_dialog.py` | `ReportDialog` | Reporte diario (ventas + ingresos por paquete) |
| `add_combo_dialog.py` | `AddComboDialog` | Crear/editar/eliminar combo (guarda en custom_combos.json) |
| `cortesia_dialog.py` | `CortesiaDialog` | Dialog de cortesia (paquete + nombre + motivo + PIN) |
| `anular_dialog.py` | `AnularDialog` | Dialog de anulacion (motivo + PIN) |
| `supervisor_pin_dialog.py` | `SupervisorPinDialog` | Teclado numerico para PIN + motivo obligatorio |
| `ticket_config_dialog.py` | `TicketConfigDialog` | Config avanzada del ticket termico |
| `config_auth_dialog.py` | `ConfigAuthDialog` | Autenticacion elevada para acceder a config |
| `kb_shortcuts.py` | `bind_entry()` | Atajos de teclado para entries |

## `MainWindow` — Layout (sin tabs)
```
┌──────────────────────────────────────────────────────┐
│  [← Volver]  [Cajero ▼]         [DD/MM/YYYY HH:MM] [●] │  ← Header 52px
├──────────────────────────────────────────────────────┤
│                                                      │
│              [Content Area - expande]                 │
│                                                      │
│  "packages" → PackageSelector mode="sale"            │
│  "config"   → PackageSelector mode="config"          │
│  "activas"  → ActiveWristbandsTab                    │
│  "printer"  → PrinterPanel (toggle, dropdowns)       │
│                                                      │
└──────────────────────────────────────────────────────┘
```

### Header
- **Izquierda**: Boton "← Volver" (solo visible fuera de "packages"), Nombre usuario "▼" (abre menu), Badge OFFLINE
- **Derecha**: Estado servidor ● (verde/rojo), Reloj HH:MM:SS (15pt bold), Fecha DD/MM/YYYY

### Menu de usuario (CTkToplevel popup)
```
┌────────────────────┐
│  [Nombre Usuario]  │  ← 18pt bold
│  [Rol]             │  ← 13pt
├────────────────────┤
│  Configuracion     │  → vista "config" (requiere permiso lb_paquetes o auth elevada)
│  Activas           │  → vista "activas"
│  Impresora         │  → vista "printer"
├────────────────────┤
│  Cerrar sesion     │  → logout (rojo)
└────────────────────┘
```
- Click fuera del popup lo cierra (bind `<Button-1>` con delay 200ms)

### API publica
```python
set_clock(time_str, date_str)     # Actualizado cada 1s desde main.py
set_server_status(reachable)      # Indicador verde/rojo
```

## `PackageSelector` — Grid de combos
### Modo "sale" (pantalla principal)
- Grid 2 columnas de `PackageCard` con canvas artistico (blobs organicos)
- Cada tarjeta muestra: nombre, precio, minutos, hardware_id (GG.BB.RR)
- Combos no asignados (assigned=False) → paleta deshabilitada, sin clic
- **Toolbar inferior**: 4 botones que requieren PIN:
  - **Cortesia** (accion=`cortesia`) → PIN → CortesiaDialog
  - **Anulaciones** (accion=`anulacion`) → PIN → picker → AnularDialog
  - **Reprogramar** (accion=`reprogram`) → PIN → picker → ReprogramarDialog
  - **Configuracion** (accion=`cortesia`) → PIN → ConfigDialog
- Polling agente cada 10s (`_poll_agent`)
- **Overlay offline**: cuando agente no responde → overlay con boton "Reconectar"
- **Chequeo de stock**: si no hay manillas disponibles → popup + permiso `lb_sobreventa`

### Modo "config" (configuracion de combos)
- Mismo grid pero sin toolbar
- Clic en combo → `AddComboDialog` en modo edicion
- Boton `+` (`AddCard`) → `AddComboDialog` en modo creacion

## `SaleDialog` — Modal de venta
- CTkToplevel modal, centrado, overlay oscuro
- **Estados**: `IDLE` → `WAITING` → `OK` | `TIMEOUT` | `ERROR`
- **Campos**: nombre cliente (opcional), referencia (opcional), metodo de pago
- **Flujo**:
  1. Clic VENDER → hilo `_do_sell()` (no bloquea UI)
  2. `self.after(200, self._poll)` → anima "Acerque la pulsera..."
  3. Si TEST_MODE o combo custom → `api.program_via_agent(G,B,R)`; si produccion → `api.sell()`
  4. Si OK → `wristband_tracker.add()` → cierre auto 2.5s
  5. Si timeout → boton "Reintentar" → `api.retry_sell(session_id)`
- No se puede cerrar durante `STATE_WAITING`

## `ActiveWristbandsTab` — Pulseras activas
- **Refresh**: `self.after(500ms, self._tick)` — loop perpetuo
- **Header**: Stats de stock en `side="right"` (packed PRIMERO)
  - Labels: `stock | listas | activas | vencidas | retirar`
  - Boton ✎ → editar stock total
- **Columnas**: Nombre(170) | Codigo(70) | Entrada(60) | Salida(60) | Estado(200) | Alerta(164) | Devolver(84)
- **Orden critico en `_tick()`**:
  1. Eliminar filas devueltas
  2. Agregar filas nuevas
  3. `_update_bar()` → `get_phase()` → actualiza `entry["status"]`
  4. Procesar `_deact_pending` (DESPUES del paso 3)
  5. `_update_stats()`
- **Deactivation poller**: hilo daemon cada 2s → `GET /deactivations` → `_deact_pending[0] += count`

## `SupervisorPinDialog` — Autorizacion PIN
- CTkToplevel 360x580, modal con `grab_set()`
- Parametros: `reason` (texto), `accion` (cortesia|anulacion|reprogram), `on_result(bool)`
- **Campos**: motivo (obligatorio), PIN (teclado numerico en pantalla + teclado fisico)
- **Flujo**: motivo + PIN (min 4 digitos) → POST /api/supervisor/validar-pin → ok/error
- Soporta reintentos sin cerrar

## `CortesiaDialog` — Cortesia
- CTkToplevel 460x570, estados: IDLE → WAITING → OK | TIMEOUT | ERROR
- **Campos**: paquete (dropdown), nombre cliente, motivo, PIN supervisor
- Flujo: valida → POST /api/cortesia → espera pulsera → resultado

## `AnularDialog` — Anulacion
- CTkToplevel 400x420, muestra info de pulsera seleccionada
- **Campos**: motivo, PIN supervisor
- Flujo: valida → POST /api/sell/{id}/anular → resultado

## `AddComboDialog` — Crear/editar combos
- Overlay oscuro sobre ventana principal, card centrada
- **Campos**: ID (auto-generado como `{agent_id}-{NN}`), color pickers (color1, color2, text_color), bold, font_size
- **Vista previa**: canvas con fondo abstracto reactivo
- Guarda en `app/data/custom_combos.json`
- En modo edicion: boton "Eliminar combo" (rojo)
- Registra combo en servidor: `api.register_combo(id)`

## `ConfigDialog`
- CTkToplevel modal, 460x420
- Campos: server_url, agent_url, stock_total, venue_id, agent_id
- Boton "Probar conexion" → test
- Boton "Guardar" → `api.save_config()`

## `ReportDialog`
- CTkToplevel, 480x520
- Carga `GET /api/reports/daily`; si falla → fallback `wristband_tracker.get_all()`
- Muestra: fecha, total ventas, total ingresos, tabla por paquete

## Vista Impresora (`MainWindow._build_printer_view`)
- Toggle habilitar/deshabilitar impresora (CTkSwitch)
- Dropdown impresoras detectadas + boton refresh "↺"
- Dropdown tamano de papel (80mm / 58mm)
- Boton "Guardar cambios" → `printing.save_config()`
- Boton "Configuracion avanzada" → `TicketConfigDialog`

## Colores de fase
```python
PHASE_COLORS = {
    "grace":   "#4caf50",   # verde
    "playing": "#2196f3",   # azul
    "expired": "#f44336",   # rojo
}
```

## Paleta de la app
```python
BG_MAIN    = "#060b18"   # fondo principal (deep navy)
BG_HEADER  = "#0a1628"   # header/panels
BG_CARD    = "#0f1d32"   # cards/surfaces
BG_HOVER   = "#152540"   # hover/active
ACCENT     = "#00d4ff"   # cian electrico
SUCCESS    = "#00e676"   # verde neon
TEXT_PRI   = "#e8edf5"   # texto primario
TEXT_SEC   = "#7a8ba8"   # texto secundario
ERROR      = "#ff4444"   # error
WARNING    = "#ffab00"   # warning
```

## Relacionado
- [→ CONTEXTO_APP.md](../CONTEXTO_APP.md) — Modulo app raiz #app
- [→ CONTEXTO_DATA.md](../data/CONTEXTO_DATA.md) — config.json, custom_combos.json #data
- [→ CONTEXTO_AGENT.md](../../agent/CONTEXTO_AGENT.md) — agente que recibe las programaciones #agent

## Tags de navegacion
#ui #customtkinter #dark-mode #deep-navy #sale-dialog #active-tab #stock #overlay #offline #phase-colors #threading #supervisor-pin #cortesia #anulacion #combo-editor #printer
