# Arquitectura de Tablas en zcatgui > **Documento de referencia definitivo** para entender, usar y extender los widgets de tabla. > Actualizado: 2025-12-27 --- ## Resumen Ejecutivo zcatgui proporciona **dos widgets de tabla** que comparten lógica común: | Widget | Uso | Datos | |--------|-----|-------| | **AdvancedTable** | Tablas pequeñas/medianas | En memoria (ArrayList) | | **VirtualAdvancedTable** | Tablas grandes (60k+ filas) | Paginados via DataProvider | **Principio DRY:** Toda la lógica común está en `table_core.zig`. Los widgets adaptan esa lógica a su modelo de datos. --- ## Arquitectura de Capas ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ APLICACIÓN │ │ (zsimifactu, etc.) │ │ - Usa AdvancedTable o VirtualAdvancedTable según necesidad │ │ - Implementa DataProvider para VirtualAdvancedTable │ │ - Maneja lógica de negocio (guardar en BD, validar, etc.) │ └─────────────────────────────────────────────────────────────────────────┘ │ │ usa ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ WIDGETS DE TABLA │ │ │ │ ┌─────────────────────┐ ┌──────────────────────────┐ │ │ │ AdvancedTable │ │ VirtualAdvancedTable │ │ │ │ │ │ │ │ │ │ - Datos en memoria │ │ - Datos paginados │ │ │ │ - selected_row/col │ │ - selected_id + active_col│ │ │ │ - Ordenación local │ │ - Scroll virtual │ │ │ │ - Multi-selección │ │ - FilterBar integrada │ │ │ └──────────┬──────────┘ └────────────┬─────────────┘ │ │ │ │ │ │ │ ambos usan │ │ │ └──────────────┬───────────────────┘ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ table_core.zig │ │ │ │ │ │ │ │ LÓGICA COMÚN (un solo lugar para modificar): │ │ │ │ - calculateNextCell() / calculatePrevCell() [Tab navigation] │ │ │ │ - toggleSort() [Ordenación] │ │ │ │ - handleEditingKeyboard() [Edición celda] │ │ │ │ - detectDoubleClick() [Doble-click] │ │ │ │ - drawCellActiveIndicator() [Renderizado] │ │ │ │ - drawEditingOverlay() [Overlay edición] │ │ │ │ - blendColor(), startsWithIgnoreCase() [Utilidades] │ │ │ │ │ │ │ │ TIPOS COMPARTIDOS: │ │ │ │ - TabNavigateResult, CellPosition │ │ │ │ - SortDirection, SortToggleResult │ │ │ │ - TableColors, CellRenderInfo, EditState │ │ │ │ - EditKeyboardResult, DoubleClickState │ │ │ └─────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────┘ ``` --- ## Estructura de Archivos ``` src/widgets/ ├── table_core.zig # LÓGICA COMÚN - modificar aquí │ ├── advanced_table/ # Tabla con datos en memoria │ ├── advanced_table.zig # Widget principal │ ├── state.zig # Estado (usa table_core) │ ├── types.zig # Tipos específicos │ └── schema.zig # Definición de columnas │ └── virtual_advanced_table/ # Tabla con datos paginados ├── virtual_advanced_table.zig # Widget principal ├── state.zig # Estado (usa table_core) ├── types.zig # Tipos específicos └── data_provider.zig # Interface para datos ``` --- ## Cuándo Usar Cada Widget ### AdvancedTable **Usar cuando:** - Datos caben en memoria (< 10,000 filas típicamente) - Necesitas ordenación/filtrado local rápido - Multi-selección de filas (Ctrl+Click, Shift+Click) - Los datos ya están cargados **Ejemplo:** Lista de tipos de IVA, categorías, configuraciones. ```zig const state = AdvancedTableState.init(allocator); defer state.deinit(); // Cargar datos try state.setRows(&my_rows); // En draw(): const result = advancedTable(ctx, rect, &state, &schema, colors); if (result.selection_changed) { // Manejar selección } ``` ### VirtualAdvancedTable **Usar cuando:** - Miles o millones de filas (60k+ poblaciones, logs, etc.) - Datos vienen de BD/API con paginación - Necesitas FilterBar integrada - Memoria limitada **Ejemplo:** Lista de poblaciones, historial de documentos. ```zig var state = VirtualAdvancedTableState{}; const provider = MyDataProvider.init(db); // En draw(): const result = virtualAdvancedTableRect(ctx, rect, &state, provider.toDataProvider(), config); if (result.selection_changed) { // Manejar selección } ``` --- ## Cómo Extender la Funcionalidad ### Regla de Oro (Norma #7 DRY) > **Si la funcionalidad es común a ambas tablas, va en `table_core.zig`.** > Los widgets solo adaptan el resultado a su modelo de datos. ### Ejemplo: Añadir nueva navegación **PASO 1:** Añadir la lógica en `table_core.zig` ```zig // table_core.zig /// Calcula la celda de la siguiente página pub fn calculatePageDown( current_row: usize, current_col: usize, num_rows: usize, page_size: usize, ) CellPosition { const new_row = @min(current_row + page_size, num_rows - 1); return .{ .row = new_row, .col = current_col, .result = .navigated }; } ``` **PASO 2:** Usar en ambos states (adaptan a su modelo) ```zig // advanced_table/state.zig pub fn pageDown(self: *AdvancedTableState, page_size: usize) void { const pos = table_core.calculatePageDown( @intCast(self.selected_row), @intCast(self.selected_col), self.getRowCount(), page_size, ); self.selected_row = @intCast(pos.row); } // virtual_advanced_table/state.zig pub fn pageDown(self: *Self, page_size: usize) void { const current_row = self.getSelectedRow() orelse 0; const pos = table_core.calculatePageDown(current_row, self.active_col, total_rows, page_size); // Adaptar a scroll virtual... } ``` ### Ejemplo: Añadir nuevo renderizado ```zig // table_core.zig /// Dibuja indicador de celda con error de validación pub fn drawCellErrorIndicator( ctx: *Context, x: i32, y: i32, width: u32, height: u32, ) void { // Borde rojo ctx.pushCommand(Command.rectOutline(x, y, width, height, Color.rgb(255, 0, 0))); // Icono de error en esquina ctx.pushCommand(Command.text(x + width - 12, y + 2, "!", Color.rgb(255, 0, 0))); } ``` --- ## API de table_core.zig ### Navegación Tab ```zig /// Resultado de navegación pub const TabNavigateResult = enum { navigated, tab_out }; /// Posición de celda calculada pub const CellPosition = struct { row: usize, col: usize, result: TabNavigateResult }; /// Calcula siguiente celda (Tab) pub fn calculateNextCell(current_row, current_col, num_cols, num_rows, wrap) CellPosition /// Calcula celda anterior (Shift+Tab) pub fn calculatePrevCell(current_row, current_col, num_cols, num_rows, wrap) CellPosition ``` ### Ordenación ```zig pub const SortDirection = enum { none, ascending, descending }; pub const SortToggleResult = struct { column: ?usize, direction: SortDirection }; /// Calcula nuevo estado de ordenación al click en columna pub fn toggleSort(current_column, current_direction, clicked_column) SortToggleResult ``` ### Edición de Celda ```zig pub const EditKeyboardResult = struct { committed: bool, // Enter presionado cancelled: bool, // Escape 2x reverted: bool, // Escape 1x (revertir a original) navigate_next: bool, // Tab navigate_prev: bool, // Shift+Tab text_changed: bool, // Texto modificado }; /// Procesa teclado en modo edición pub fn handleEditingKeyboard(ctx, edit_buffer, edit_len, edit_cursor, escape_count, original_text) EditKeyboardResult ``` ### Renderizado ```zig /// Dibuja indicador de celda activa pub fn drawCellActiveIndicator(ctx, x, y, width, height, row_bg, colors, has_focus) void /// Dibuja overlay de edición pub fn drawEditingOverlay(ctx, x, y, width, height, edit_text, cursor_pos, colors) void /// Dibuja texto de celda pub fn drawCellText(ctx, x, y, width, height, text, color, text_align) void ``` ### Utilidades ```zig /// Detecta doble-click pub fn detectDoubleClick(state, current_time, row, col) bool /// Mezcla dos colores pub fn blendColor(base, overlay, alpha) Color /// Compara strings case-insensitive pub fn startsWithIgnoreCase(haystack, needle) bool ``` --- ## Modelo de Datos ### AdvancedTable ```zig pub const AdvancedTableState = struct { // Datos rows: ArrayListUnmanaged(Row), // Selección (por ÍNDICE) selected_row: i32, // -1 = ninguna selected_col: i32, // Multi-selección selected_rows: [128]u8, // Bitset para 1024 filas // Edición editing: bool, edit_buffer: [256]u8, edit_cursor: usize, // Ordenación sort_column: i32, sort_direction: SortDirection, }; ``` ### VirtualAdvancedTable ```zig pub const VirtualAdvancedTableState = struct { // Selección (por ID, no índice) selected_id: ?i64, // ID del registro active_col: usize, // Columna activa // Ventana virtual scroll_offset: usize, // Offset en filas current_window: []RowData, // Datos visibles window_start: usize, // Índice inicial de ventana // Edición editing_cell: ?CellId, edit_buffer: [256]u8, row_dirty: bool, // Filtro filter_buf: [256]u8, active_chips: u16, // Bitset de chips activos }; ``` --- ## Tests Los tests de `table_core.zig` verifican la lógica común: ```bash cd zcatgui && zig build test ``` Tests incluidos: - `calculateNextCell` - navegación básica, wrap, tab_out - `calculatePrevCell` - navegación básica, wrap, tab_out - `toggleSort` - ciclo de direcciones, cambio de columna - `detectDoubleClick` - detección correcta de doble-click - `blendColor` - mezcla de colores - `startsWithIgnoreCase` - búsqueda case-insensitive --- ## Historial de Cambios | Fecha | Cambio | |-------|--------| | 2025-12-27 | Refactorización DRY: lógica común movida a table_core.zig | | 2025-12-26 | table_core.zig creado con funciones de renderizado compartidas | | 2025-12-17 | VirtualAdvancedTable añadido para tablas grandes | | 2025-12-16 | AdvancedTable implementado con CRUD Excel-style | --- ## Referencias - `src/widgets/table_core.zig` - Código fuente de lógica común - `src/widgets/advanced_table/` - AdvancedTable completo - `src/widgets/virtual_advanced_table/` - VirtualAdvancedTable completo - `docs/ADVANCED_TABLE_DESIGN.md` - Diseño original (histórico)