Apply TEAM_STANDARDS Norma #25: project names use lowercase everywhere - repo, directory, module, documentation, imports. No CamelCase in project names. Consistency = less cognitive friction. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
617 lines
15 KiB
Markdown
617 lines
15 KiB
Markdown
# Análisis: Widgets y Funcionalidades de Fyne en Simifactu
|
|
|
|
> Investigación realizada: 2025-12-09
|
|
> Propósito: Extraer requisitos reales de una aplicación de producción
|
|
|
|
---
|
|
|
|
## Resumen Ejecutivo
|
|
|
|
**Simifactu** es una aplicación de facturación empresarial desarrollada en Go con Fyne v2. Este análisis extrae todos los widgets, layouts, y funcionalidades que zcatgui necesitaría para soportar una aplicación similar.
|
|
|
|
**Proyecto analizado**: `/mnt/cello2/arno/re/recode/go/simifactu/`
|
|
|
|
---
|
|
|
|
## 1. Widgets Básicos Usados
|
|
|
|
### 1.1 Entry Widgets (Campos de Texto)
|
|
|
|
**Uso masivo**: Prácticamente todos los paneles usan Entry fields.
|
|
|
|
**Variantes detectadas:**
|
|
- `widget.Entry` - Campo de texto básico (single-line)
|
|
- `widget.Entry` con `MultiLine = true` - Texto multi-línea
|
|
- **AutoComplete Entry** (custom) - Entry con dropdown de sugerencias
|
|
|
|
**Características críticas:**
|
|
- `OnChanged` callback ejecutado en cada tecla
|
|
- `OnSubmit` callback al presionar Enter
|
|
- `PlaceHolder` text
|
|
- `Validation` callbacks
|
|
- **ReadOnly** mode
|
|
- `FocusGained/FocusLost` callbacks
|
|
|
|
**Requisito zcatgui:**
|
|
```zig
|
|
pub const Input = struct {
|
|
text: []const u8,
|
|
cursor: usize,
|
|
placeholder: []const u8,
|
|
multiline: bool,
|
|
suggestions: ?[][]const u8, // Para autocomplete
|
|
on_changed: ?*const fn([]const u8) void,
|
|
on_submit: ?*const fn([]const u8) void,
|
|
readonly: bool,
|
|
};
|
|
```
|
|
|
|
### 1.2 Button Widgets
|
|
|
|
**Variantes detectadas:**
|
|
- `widget.Button` - Botón estándar
|
|
- **Button3D** (custom) - Botón con efecto 3D, altura reducida
|
|
|
|
**Usos identificados:**
|
|
- Botones CRUD: Nuevo, Guardar, Eliminar
|
|
- Navegación: `<<`, `<`, `>`, `>>`
|
|
- Acciones contextuales: Exportar, Importar, Duplicar
|
|
|
|
**Requisito zcatgui:**
|
|
```zig
|
|
pub const Button = struct {
|
|
label: []const u8,
|
|
on_tapped: *const fn() void,
|
|
disabled: bool,
|
|
style: Style,
|
|
importance: enum { primary, secondary, danger },
|
|
};
|
|
```
|
|
|
|
### 1.3 Select/Dropdown Widgets
|
|
|
|
**Variantes detectadas:**
|
|
- `widget.Select` - Dropdown básico
|
|
- **SelectEntry** - Combo editable (dropdown + entry)
|
|
|
|
**Usos identificados:**
|
|
- Tipo IVA: "21%", "10%", "4%", "0%", "Exento"
|
|
- Régimen Equivalencia
|
|
- Tipo Documento: "Presupuesto", "Albarán", "Factura"
|
|
- Estado Documento: "Borrador", "Confirmado", "Enviado"
|
|
- Forma de Pago
|
|
|
|
**Requisito zcatgui:**
|
|
```zig
|
|
pub const Select = struct {
|
|
options: [][]const u8,
|
|
selected: usize,
|
|
on_changed: *const fn(usize) void,
|
|
placeholder: []const u8,
|
|
allow_custom: bool,
|
|
};
|
|
```
|
|
|
|
### 1.4 Checkbox
|
|
|
|
**Usos detectados:**
|
|
- "Es Sociedad"
|
|
- Selección múltiple en importación
|
|
|
|
**Requisito zcatgui:**
|
|
```zig
|
|
pub const Checkbox = struct {
|
|
checked: bool,
|
|
label: []const u8,
|
|
on_changed: *const fn(bool) void,
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Widgets Avanzados/Complejos
|
|
|
|
### 2.1 Table Widget ⭐ CRÍTICO
|
|
|
|
**El widget MÁS importante** de Simifactu - usado en prácticamente todas las vistas.
|
|
|
|
**Implementación actual:**
|
|
- Fork personalizado de `fyne/widget/table.go`
|
|
- **AdvancedTable** wrapper (1000+ líneas)
|
|
|
|
**Características implementadas (TODAS necesarias):**
|
|
|
|
#### Virtualización y Performance
|
|
- Lazy rendering (solo filas visibles)
|
|
- Scroll eficiente con cache de celdas
|
|
- Invalidación selectiva de cache
|
|
|
|
#### Navegación Teclado
|
|
```
|
|
Flechas: Up/Down/Left/Right (navegación entre celdas)
|
|
Enter: Editar celda actual
|
|
Space: Activar edición con celda vacía
|
|
Tab: Siguiente celda editable
|
|
Escape: Cancelar edición / Revertir cambios
|
|
Ctrl+N: Nueva fila
|
|
Ctrl+B/Supr: Borrar fila
|
|
Home/End: Primera/Última fila
|
|
```
|
|
|
|
#### Edición In-Situ
|
|
- Doble-click para editar celda
|
|
- Entry overlay que aparece sobre celda
|
|
- Auto-submit al cambiar fila
|
|
- Validación por columna
|
|
- **Tipos de celda**: Text, Number, Money, Date, Select
|
|
|
|
#### Indicadores Visuales de Estado
|
|
- Filas nuevas: Icono verde
|
|
- Filas modificadas: Icono naranja
|
|
- Filas borradas: Icono rojo
|
|
|
|
#### Ordenamiento (Sorting)
|
|
- Click en header para ordenar
|
|
- Indicadores visuales ▲/▼
|
|
|
|
#### Colores Dinámicos
|
|
```zig
|
|
pub const TableColors = struct {
|
|
header_background: Color,
|
|
header_text: Color,
|
|
row_normal: Color,
|
|
row_hover: Color,
|
|
cell_active: Color,
|
|
cell_editing: Color,
|
|
selection: Color,
|
|
};
|
|
```
|
|
|
|
#### Schema-Driven Configuration
|
|
```zig
|
|
pub const TableSchema = struct {
|
|
columns: []ColumnDef,
|
|
show_state_indicators: bool,
|
|
allow_keyboard_nav: bool,
|
|
allow_edit: bool,
|
|
allow_sort: bool,
|
|
};
|
|
|
|
pub const ColumnDef = struct {
|
|
name: []const u8,
|
|
width: f32,
|
|
column_type: enum { text, number, money, date, select },
|
|
editable: bool,
|
|
validator: ?*const fn([]const u8) bool,
|
|
};
|
|
```
|
|
|
|
### 2.2 InnerWindow Widget ⭐ CRÍTICO
|
|
|
|
**Fork personalizado de Fyne** - contenedor con barra de título y bordes.
|
|
|
|
**Características:**
|
|
- Título personalizable y dinámico
|
|
- Barra de título con color configurable
|
|
- Borde exterior con color configurable
|
|
- Fondo contenido separado
|
|
- Botón cerrar opcional
|
|
- Callbacks: `OnFocusGained`, `OnFocusLost`, `OnTappedBar`
|
|
- Resize programático
|
|
|
|
**Uso en Simifactu:**
|
|
- Todos los paneles principales están en InnerWindows
|
|
- Layout: 3-4 InnerWindows en HSplit/VSplit
|
|
|
|
**Requisito zcatgui:**
|
|
```zig
|
|
pub const Panel = struct {
|
|
title: []const u8,
|
|
content: Widget,
|
|
border: BorderStyle,
|
|
title_style: Style,
|
|
closable: bool,
|
|
on_focus_gained: ?*const fn() void,
|
|
on_focus_lost: ?*const fn() void,
|
|
};
|
|
```
|
|
|
|
### 2.3 List Widget
|
|
|
|
**Uso detectado:**
|
|
- Navegador estilo OpenOffice (carpetas izquierda, archivos derecha)
|
|
|
|
**Requisito zcatgui:**
|
|
```zig
|
|
pub const List = struct {
|
|
items: []ListItem,
|
|
selected: i32,
|
|
on_selected: *const fn(usize) void,
|
|
};
|
|
|
|
pub const ListItem = struct {
|
|
text: []const u8,
|
|
icon: ?Symbol,
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Layouts Usados
|
|
|
|
### 3.1 Container Layouts (Fyne)
|
|
|
|
```go
|
|
// Border - contenido con bordes fijos
|
|
container.NewBorder(top, bottom, left, right, center)
|
|
|
|
// VBox/HBox - stacks
|
|
container.NewVBox(widget1, widget2, widget3)
|
|
container.NewHBox(widget1, widget2)
|
|
|
|
// Grid
|
|
container.NewGridWithColumns(3, campo1, campo2, campo3)
|
|
|
|
// HSplit/VSplit - CRÍTICO
|
|
split := container.NewHSplit(leftPanel, rightPanel)
|
|
split.SetOffset(0.25) // 25% izquierda
|
|
|
|
// Stack - overlay
|
|
container.NewStack(background, content, overlayPopup)
|
|
|
|
// Padded
|
|
container.NewPadded(widget)
|
|
```
|
|
|
|
### 3.2 Layout Principal de Simifactu
|
|
|
|
```
|
|
┌───────────────────────────────────────────────────────────┐
|
|
│ Status Line (Border Top - altura fija) │
|
|
├─────────┬─────────────────────┬────────────────────────────┤
|
|
│ WHO │ WHO Detail │ Document Detail (FULL) │
|
|
│ List │ ─────────────────── │ - Cabecera (40%) │
|
|
│ (20%) │ Documents List │ - Líneas (60%) │
|
|
│ │ │ │
|
|
│ │ (VSplit 50/50) │ (VSplit integrado) │
|
|
│ │ (35%) │ (45%) │
|
|
└─────────┴─────────────────────┴────────────────────────────┘
|
|
HSplit(0.20) HSplit(0.4347)
|
|
```
|
|
|
|
**Requisito zcatgui:**
|
|
```zig
|
|
pub const Split = struct {
|
|
direction: enum { horizontal, vertical },
|
|
children: [2]Widget,
|
|
offset: f32, // 0.0-1.0
|
|
draggable: bool, // Ctrl+flechas para ajustar
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Diálogos y Popups
|
|
|
|
### 4.1 Modal Dialogs Detectados
|
|
|
|
```go
|
|
// Confirmación (Yes/No)
|
|
dialog.ShowConfirm("Eliminar", "¿Seguro?", callback, window)
|
|
|
|
// Información (solo OK)
|
|
dialog.ShowInformation("Título", "Mensaje", window)
|
|
|
|
// Error (solo OK)
|
|
dialog.ShowError(err, window)
|
|
|
|
// Entrada de texto
|
|
dialog.ShowEntryDialog("Nombre", "Introduzca:", "default", callback, window)
|
|
|
|
// File picker
|
|
dialog.ShowFileOpen(callback, window)
|
|
|
|
// Folder picker
|
|
dialog.ShowFolderOpen(callback, window)
|
|
```
|
|
|
|
### 4.2 Custom Dialogs Detectados
|
|
|
|
- **Import Window** - Navegador con checkboxes
|
|
- **Export Window** - Selector formato + opciones
|
|
- **Edit Date Dialog** - Picker fecha con calendario
|
|
- **Empresas Window** - Gestión multi-empresa
|
|
|
|
---
|
|
|
|
## 5. Eventos y Callbacks
|
|
|
|
### 5.1 Eventos Teclado ⭐ MUY IMPORTANTE
|
|
|
|
**OnTypedKey** - Usado en TODOS los paneles:
|
|
|
|
```go
|
|
window.Canvas().SetOnTypedKey(func(key *fyne.KeyEvent) {
|
|
// Shortcuts globales
|
|
if key.Name == fyne.KeyF2 {
|
|
showEmpresasWindow()
|
|
return
|
|
}
|
|
|
|
// Ctrl+1/2/3 - Layout presets
|
|
if mods&fyne.KeyModifierControl != 0 {
|
|
switch key.Name {
|
|
case "1": applyLayout1()
|
|
case "2": applyLayout2()
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
**Requisito zcatgui:**
|
|
```zig
|
|
pub const KeyEvent = struct {
|
|
key: Key,
|
|
modifiers: KeyModifiers,
|
|
};
|
|
|
|
pub const KeyModifiers = packed struct {
|
|
control: bool,
|
|
alt: bool,
|
|
shift: bool,
|
|
};
|
|
|
|
// Handler retorna si consume el evento
|
|
pub const OnKey = *const fn(KeyEvent) bool;
|
|
```
|
|
|
|
### 5.2 Eventos Mouse
|
|
|
|
```go
|
|
widget.OnTapped = func(*fyne.PointEvent) { click() }
|
|
table.OnCellDoubleTapped = func(row, col int) { edit(row, col) }
|
|
widget.OnMouseIn = func(*desktop.MouseEvent) { hover() }
|
|
widget.OnMouseOut = func(*desktop.MouseEvent) { unhover() }
|
|
```
|
|
|
|
### 5.3 Focus Events
|
|
|
|
```go
|
|
widget.OnFocusGained = func() { activate() }
|
|
widget.OnFocusLost = func() { deactivate() }
|
|
canvas.Focus(widget) // Programático
|
|
```
|
|
|
|
---
|
|
|
|
## 6. El Problema fyne.Do() ⭐⭐⭐ CRÍTICO
|
|
|
|
### 6.1 El Problema del Threading
|
|
|
|
**402 usos de `fyne.Do()`** detectados en el proyecto.
|
|
|
|
**Razón**: Fyne requiere que TODAS las operaciones UI se ejecuten en el thread principal.
|
|
|
|
```go
|
|
// PATTERN OBLIGATORIO en Fyne
|
|
go func() {
|
|
result := heavyComputation() // Background
|
|
|
|
fyne.Do(func() {
|
|
label.SetText(result) // DEBE estar en fyne.Do()
|
|
table.Refresh()
|
|
})
|
|
}()
|
|
```
|
|
|
|
**Errores si no se usa fyne.Do():**
|
|
```
|
|
panic: concurrent map writes
|
|
SIGSEGV: segmentation fault
|
|
GL context not current
|
|
```
|
|
|
|
### 6.2 Por qué zcatgui NO tiene este problema
|
|
|
|
**Immediate mode es inherentemente thread-safe por diseño:**
|
|
- No hay estado compartido del framework
|
|
- Buffer es solo memoria (no GL context)
|
|
- Render es single-threaded
|
|
- No necesita equivalente a fyne.Do()
|
|
|
|
**Pero sí necesitamos:**
|
|
- AsyncLoop para queries BD
|
|
- Timer system para auto-save
|
|
- Event queue
|
|
|
|
---
|
|
|
|
## 7. Theming y Colores
|
|
|
|
### 7.1 Sistema de Colores Dual
|
|
|
|
**Sistema 1: Colores por Panel:**
|
|
```go
|
|
type PanelColors struct {
|
|
Fondo Color
|
|
Header Color
|
|
Texto Color
|
|
Entry_Fondo Color
|
|
Entry_Texto Color
|
|
Button_Fondo Color
|
|
Tabla_Header Color
|
|
Celda_Activa Color
|
|
// ... 20+ colores por panel
|
|
}
|
|
|
|
dataManager.GetColorForPanel("who_detail", "Fondo")
|
|
```
|
|
|
|
**Sistema 2: Colores Globales Botones:**
|
|
```go
|
|
type ButtonColors struct {
|
|
ActivoButtonFondo Color
|
|
InactivoButtonFondo Color
|
|
NuevoButtonFondo Color
|
|
EliminarButtonFondo Color
|
|
}
|
|
```
|
|
|
|
### 7.2 Hot-Reload de Colores
|
|
|
|
```go
|
|
// Panel se registra como observer
|
|
dataManager.RegisterColorObserver("who_detail", panel)
|
|
|
|
// Cuando usuario cambia color
|
|
dataManager.NotifyColorsChange("who_detail")
|
|
|
|
// Panel actualiza
|
|
func (p *Panel) NotifyColorsChange() {
|
|
p.applyColors()
|
|
}
|
|
```
|
|
|
|
### 7.3 Persistencia
|
|
|
|
- Archivo texto KV: `who_detail.Fondo = 40,44,52`
|
|
- JSON backup
|
|
- Base de datos: tabla `config_backup`
|
|
|
|
---
|
|
|
|
## 8. Características Especiales
|
|
|
|
### 8.1 Auto-Save System
|
|
|
|
```go
|
|
dataManager.StartAutoSaveTimer()
|
|
// Guarda config cada N segundos si isDirty == true
|
|
```
|
|
|
|
### 8.2 File Watcher (Hot-Reload Config)
|
|
|
|
```go
|
|
dataManager.StartConfigFileWatcher()
|
|
// Detecta cambios externos, recarga automáticamente
|
|
```
|
|
|
|
### 8.3 Export/Import Data
|
|
|
|
- JSON completo (clientes + documentos + líneas)
|
|
- Exportación selectiva (checkboxes)
|
|
- Importación con merge
|
|
- Progress tracking
|
|
|
|
### 8.4 Multi-Empresa
|
|
|
|
- Tabla `empresas` en BD
|
|
- Selector empresa activa
|
|
- Datos aislados por empresa_uuid
|
|
|
|
### 8.5 Templates Sistema
|
|
|
|
- Plantillas PDF personalizables
|
|
- Editor visual
|
|
- Vista previa en tiempo real
|
|
|
|
---
|
|
|
|
## 9. Widgets Third-Party (fynex-widgets)
|
|
|
|
**Librería vendorizada**: `/third_party/fynex-widgets/`
|
|
|
|
| Widget | Descripción | Uso |
|
|
|--------|-------------|-----|
|
|
| **AutoComplete** | Entry con dropdown sugerencias | Población, Provincia, País |
|
|
| **DateEntry** | Entry con calendar picker | Fechas |
|
|
| **NumEntry** | Entry solo números | Cantidades, precios |
|
|
| **SelectEntry** | Combo editable | Custom values |
|
|
| **Calendar** | Widget calendario | Selección fecha |
|
|
| **Tooltips** | Ayuda contextual | Hover |
|
|
|
|
---
|
|
|
|
## 10. Resumen de Requisitos para zcatgui
|
|
|
|
### 10.1 Widgets Necesarios (Orden Prioridad)
|
|
|
|
| # | Widget | Prioridad | Notas |
|
|
|---|--------|-----------|-------|
|
|
| 1 | **Table** | CRÍTICA | Edición in-situ, state indicators, sorting |
|
|
| 2 | **Input** | CRÍTICA | Autocomplete, validation |
|
|
| 3 | **Select** | CRÍTICA | Dropdown selection |
|
|
| 4 | **Panel** | ALTA | Título dinámico, bordes coloreados |
|
|
| 5 | **Split** | ALTA | HSplit/VSplit draggable |
|
|
| 6 | **Button** | ALTA | Disabled, importance levels |
|
|
| 7 | **Modal** | MEDIA | Diálogos modales |
|
|
| 8 | **List** | MEDIA | Lista seleccionable |
|
|
| 9 | **Checkbox** | MEDIA | Toggle boolean |
|
|
| 10 | **Label** | BAJA | Texto estático |
|
|
|
|
### 10.2 Layouts Necesarios
|
|
|
|
| Layout | Status |
|
|
|--------|--------|
|
|
| VBox/HBox | Usar Layout constraints |
|
|
| Grid | Usar Layout constraints |
|
|
| HSplit/VSplit | **CRÍTICO - Implementar** |
|
|
| Stack (Overlay) | Para popups |
|
|
| Border | Emular con constraints |
|
|
|
|
### 10.3 Features Críticos
|
|
|
|
1. **Table con edición in-situ** - 80% del trabajo
|
|
2. **Split panels draggables** - Layout principal
|
|
3. **Select/Dropdown** - 20+ lugares de uso
|
|
4. **Sistema de focus** - Navegación teclado
|
|
5. **Hot-reload themes** - Cambio colores en runtime
|
|
|
|
### 10.4 Estimación de Trabajo
|
|
|
|
| Componente | Líneas | Tiempo |
|
|
|------------|--------|--------|
|
|
| Table editable | 1500 | 2-3 semanas |
|
|
| Select/Dropdown | 300 | 3-4 días |
|
|
| Split layout | 200 | 2-3 días |
|
|
| Panel widget | 150 | 2 días |
|
|
| AutoComplete | 250 | 3-4 días |
|
|
| Modal/Dialog | 200 | 2-3 días |
|
|
| **TOTAL** | ~2600 | 4-5 semanas |
|
|
|
|
---
|
|
|
|
## 11. Ventajas de Portar a GUI Immediate-Mode
|
|
|
|
### Por qué Vale la Pena
|
|
|
|
1. **Sin fyne.Do()**: Código más simple, sin threading hell
|
|
2. **Performance**: Software rendering predecible
|
|
3. **Control total**: Sin "magia" del framework
|
|
4. **Testing**: Funciones puras, fácil de testear
|
|
5. **Debugging**: Estado visible, reproducible
|
|
|
|
### Comparación Threading
|
|
|
|
| Fyne (Retained) | zcatgui (Immediate) |
|
|
|-----------------|---------------------|
|
|
| 402 usos fyne.Do() | 0 equivalentes |
|
|
| Callbacks async | Polling síncrono |
|
|
| Estado oculto | Estado explícito |
|
|
| Race conditions | Sin races |
|
|
|
|
---
|
|
|
|
## 12. Archivos Clave Analizados
|
|
|
|
```
|
|
/cmd/simifactu/main.go (881 líneas)
|
|
/internal/ui/components/advanced_table/*.go (2000+ líneas)
|
|
/internal/ui/panels_v3/panels/who_detail/ui_layout.go (300 líneas)
|
|
/internal/ui/panels_v3/panels/document_detail/ui_layout.go (400 líneas)
|
|
/internal/ui/dialogs/import_window.go (500 líneas)
|
|
/third_party/fynex-widgets/autocomplete.go (600 líneas)
|
|
/internal/ui/panels_v3/data/manager_colors.go (800 líneas)
|
|
```
|
|
|
|
**Total analizado**: ~10,000 líneas de código + 5000 líneas de docs
|