b9716a7cd6
Snapshot de WIP acumulado de sesiones previas antes de merge wave 1 del flow 0008 (kanban_cpp + agent_runner_api + DoD schema). Incluye: - dev/flows/0008-kanban-cpp-and-agent-workflows.md - dev/issues/0112-0119*.md (7 sub-issues) - WIP previo en cmd/fn/doctor.go, registry/*, modules/, cpp/, etc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
357 lines
20 KiB
Markdown
357 lines
20 KiB
Markdown
---
|
|
id: "0108"
|
|
title: "App tables_qa — testbed agresivo del modulo data_table + deprecar tables_playground"
|
|
status: in-progress
|
|
type: app
|
|
domain:
|
|
- cpp-stack
|
|
- apps-infra
|
|
- meta
|
|
scope: app
|
|
priority: alta
|
|
depends:
|
|
- "0107"
|
|
blocks: []
|
|
related:
|
|
- "0081"
|
|
- "0097"
|
|
created: 2026-05-17
|
|
updated: 2026-05-17
|
|
tags: [data-table, modules, testbed, qa, regression, version-selector, apphub]
|
|
---
|
|
|
|
# 0108 — tables_playground como testbed agresivo del modulo data_table
|
|
|
|
## Problema
|
|
|
|
`apps/primitives_gallery/playground/tables/` hoy es el playground original de tablas — fue el origen de `modules/data_table`. Pero esta DESACOPLADO del modulo: linkea sus PROPIAS copias de `data_table.cpp`, `data_table_logic.cpp`, `tql.cpp`, `tql_to_sql.cpp`, `lua_engine.cpp`, `llm_anthropic.cpp`, `viz.cpp` (versiones legacy). El `self_test.cpp` (430 checks) prueba la logica legacy, no el modulo.
|
|
|
|
Consecuencias:
|
|
- Bug en el modulo no se detecta en el playground.
|
|
- Cualquier mejora del modulo (post-0107) NO se valida contra el playground hasta que algun app lo encuentre en runtime.
|
|
- El playground es deuda — codigo duplicado que nadie mantiene a la par.
|
|
|
|
Ademas, las apps consumidoras (`app_gestion`, `data_factory`, `registry_dashboard`, `services_monitor`, etc.) usan el modulo con patrones repetidos pero no documentados como "casos de uso canonicos":
|
|
|
|
- `CellRenderer::CategoricalChip` con `chips` map (status, kind, enabled).
|
|
- `CellRenderer::ColorScale` con `range_min`/`range_max`/`range_alpha` (duracion en ms).
|
|
- `CellRenderer::Badge` con `badges` map (version pinning, status, env).
|
|
- `CellRenderer::Duration` con `duration_warn_ms` / `duration_error_ms`.
|
|
- `CellRenderer::Button` con `button_action` → consumido en `events_out` (`TableEventKind::ButtonClick`).
|
|
- `TableEventKind::RowDoubleClick` → abrir detalle / drilldown.
|
|
- Joins entre `TableInput[0]` (main) y `TableInput[i]` (joinables) con `JoinStrategy`.
|
|
- Color rules condicionales (numericas + categoricas) — declaradas en `State.stages[k].color_rules`.
|
|
|
|
NO HAY un sitio donde un agente o humano pueda ver TODOS estos patrones funcionando lado-a-lado para verificar visualmente que el modulo se comporta bien. El primitives_gallery hace eso para componentes basicos, pero no para tablas avanzadas.
|
|
|
|
## Decision (actualizada 2026-05-17)
|
|
|
|
Crear **app NUEVA `tables_qa`** (no evolucionar `tables_playground` in-place). Razon: playground tiene 8 .cpp legacy + self_test 2921 LOC mezclado. Mas limpio crear desde 0 con `init_cpp_app` + `uses_modules: [data_table_cpp]` desde el principio.
|
|
|
|
`tables_playground` se **deprecara** tras `tables_qa` verde:
|
|
- `apps/primitives_gallery/playground/tables/` → mover a `apps/primitives_gallery/playground/tables_legacy_archive/` (gitignored o conservado por historia).
|
|
- Entry en `cpp/CMakeLists.txt` que registra `tables_playground` se elimina.
|
|
- `tables_playground_self_test` deja de buildear.
|
|
|
|
### Identidad de la app
|
|
|
|
- `name`: `tables_qa`
|
|
- `description`: "Testbed agresivo del modulo data_table — multi-tabla, menu QA toggleable, inyector de eventos, counters live, version selector + downgrade side-by-side."
|
|
- `icon.phosphor`: `"test-tube"` (Phosphor — claro QA, sin ambiguedad)
|
|
- `icon.accent`: `"#f59e0b"` (amber — testing zone, distinto de los colores existentes de otras apps)
|
|
- `dir_path`: `apps/tables_qa`
|
|
- `repo_url`: `https://gitea.organic-machine.com/dataforge/tables_qa`
|
|
- `tags`: `[imgui, dashboard, qa, testing, data-table, regression]`
|
|
|
|
### Registro en App Hub
|
|
|
|
Tras build verde:
|
|
1. `./fn run generate_app_icon "test-tube" "#f59e0b" "apps/tables_qa/appicon.ico"` — genera .ico multi-res.
|
|
2. Anadir trio obligatorio (`description` + `icon.phosphor` + `icon.accent`) al `app.md` (ya cubierto en frontmatter del scaffolder).
|
|
3. `./fn run refresh_app_hub` — regenera icons PNG + manifest TSV del hub + reinicia `app_hub_launcher` si esta corriendo.
|
|
4. Verificar tarjeta visible en hub con icono amber + descripcion.
|
|
|
|
## Decision (original — refundar a aplicacion completa)
|
|
|
|
Refundar `tables_playground` como **testbed agresivo del modulo `fn_module_data_table`**:
|
|
|
|
1. **Migrar al modulo**: el playground PASA a depender de `fn_module_data_table` static lib. Eliminar las copias locales (`data_table.cpp`, `data_table_logic.cpp`, `tql.cpp`, `lua_engine.cpp`, `viz.cpp`, `tql_to_sql.cpp`, `llm_anthropic.cpp`). El playground se convierte en cliente del modulo igual que las apps reales.
|
|
2. **Multi-tabla**: 6-8 tablas demo cubriendo cada `CellRenderer` + cada `TableEventKind` + joins + color rules. Cada una con su `State` persistente. Tab navegable (`ImGui::BeginTabBar`).
|
|
3. **Menu de acciones replicando apps**: barra superior con toggles que activan/desactivan features tipicas usadas por apps:
|
|
- "Buttons en celdas (Cancel/Retry/Inspect)" → emite ButtonClick.
|
|
- "Color condicional numerico (ColorScale en duraciones)".
|
|
- "Color condicional categorico (CategoricalChip por status)".
|
|
- "Badge version pinning".
|
|
- "Duration con thresholds (warn=1000ms / error=5000ms)".
|
|
- "Dots sparkline (status timeline)".
|
|
- "Icon map (TI_CHECK/TI_X/TI_CIRCLE)".
|
|
- "Join Left/Inner/Right/Full entre tablas".
|
|
- "Row double-click → modal de detalle".
|
|
- "Row right-click → context menu".
|
|
- "TQL pipeline (filter/breakout/agg/sort) con chips".
|
|
- "Ask AI panel (TQL natural language)".
|
|
- "Save/Load TQL desde disco".
|
|
- "Export CSV".
|
|
- "Drill-down entre stages".
|
|
4. **Version selector + downgrade**:
|
|
- Mostrar version actual del modulo en header (`fn::framework_version()` / `data_table::module_version()` — anadir API).
|
|
- Selector de "modo compatibilidad" para simular comportamiento de versiones anteriores (`v1.4`, `v1.3`, etc.). Implementacion: feature flags por version dentro del modulo o branch-by-config al construir `TableInput`. Util para auditar regresiones cuando bumpemos versiones.
|
|
- Side-by-side: renderizar la misma `TableInput` con la version actual a la izquierda y con el modo compatibilidad a la derecha. Diff visual inmediato.
|
|
5. **Capture & compare visual**: integrar con el sistema `primitives_gallery --capture` existente (golden images) para que cada commit corra screenshots del testbed y compare contra master. Si pixel diff > threshold → CI rojo.
|
|
6. **Self-test del modulo**: nuevo `tables_playground_module_test.cpp` que ejerza la API publica `data_table::render()` con casos sinteticos. Headless (sin GLFW window) usando `imgui_test_engine` (si FN_BUILD_TESTS=ON). Cubre:
|
|
- Cada `CellRenderer` enum se renderiza sin crash.
|
|
- `events_out` recibe el evento correcto al simular click.
|
|
- State se mantiene entre frames.
|
|
- TQL pipeline produce output esperado.
|
|
- Joins respetan strategy.
|
|
|
|
## Arquitectura
|
|
|
|
### Estructura final del playground
|
|
|
|
```
|
|
apps/primitives_gallery/playground/tables/
|
|
CMakeLists.txt # linka fn_module_data_table; ELIMINA sources legacy
|
|
main.cpp # entry: fn::run_app + render_playground()
|
|
playground.cpp # render_playground(): tab bar + acciones menu
|
|
tables/ # NEW — 1 archivo por tabla demo
|
|
tbl_basic.cpp # Text + numerico, base case
|
|
tbl_renderers.cpp # 1 columna por cada CellRenderer (visual review)
|
|
tbl_buttons.cpp # Cancel/Retry/Inspect — emite ButtonClick
|
|
tbl_color_rules.cpp # ColorScale numerico + CategoricalChip
|
|
tbl_durations.cpp # Duration renderer + thresholds
|
|
tbl_dots.cpp # Dots sparkline (status timelines)
|
|
tbl_joins.cpp # 2 tablas + Left/Inner/Right/Full
|
|
tbl_tql.cpp # Pipeline TQL completo + Ask AI
|
|
tbl_drill.cpp # Drill-down stages
|
|
version_compat.cpp # version selector + side-by-side downgrade
|
|
test_module.cpp # NEW: self_test contra API publica del modulo
|
|
e2e_run.sh # actualizar: build + test_module + capture
|
|
(ELIMINAR: data_table.cpp, data_table_logic.cpp, tql.cpp, tql_to_sql.cpp,
|
|
lua_engine.cpp, llm_anthropic.cpp, viz.cpp, tql_duckdb.cpp, self_test.cpp)
|
|
README.md # NEW: documenta cada tabla demo + checklist e2e
|
|
```
|
|
|
|
### API publica del modulo expuesta al playground (documentar)
|
|
|
|
```cpp
|
|
// Entry function
|
|
namespace data_table {
|
|
void render(const char* id,
|
|
const std::vector<TableInput>& tables,
|
|
State& state,
|
|
std::vector<TableEvent>* events_out = nullptr,
|
|
bool show_chrome = true);
|
|
}
|
|
|
|
// Tipos publicos consumidos por las apps
|
|
struct TableInput {
|
|
std::string name;
|
|
std::vector<std::string> headers;
|
|
std::vector<ColumnType> types;
|
|
const char* const* cells; // row-major, rows*cols
|
|
int rows;
|
|
int cols;
|
|
std::vector<ColumnSpec> column_specs; // renderer config per col
|
|
};
|
|
|
|
struct ColumnSpec {
|
|
std::string id;
|
|
CellRenderer renderer = CellRenderer::Text;
|
|
// Badge / CategoricalChip / Dots
|
|
std::vector<BadgeRule> badges;
|
|
std::vector<ChipRule> chips;
|
|
// Progress
|
|
bool progress_scale_100 = false;
|
|
std::string progress_color_hex;
|
|
// Duration
|
|
float duration_warn_ms = 1000.0f;
|
|
float duration_error_ms = 5000.0f;
|
|
// Icon
|
|
std::vector<IconMapEntry> icon_map;
|
|
// Button
|
|
std::string button_action;
|
|
std::string button_label;
|
|
std::string button_color_hex;
|
|
// ColorScale
|
|
double range_min = 0.0;
|
|
double range_max = 1.0;
|
|
float range_alpha = 0.25f;
|
|
std::vector<ColorStop> color_stops;
|
|
// Tooltip
|
|
std::string tooltip;
|
|
bool tooltip_on_hover = false;
|
|
};
|
|
|
|
enum class CellRenderer : uint8_t {
|
|
Text=0, Badge=1, Progress=2, Duration=3, Icon=4, Button=5,
|
|
Dots=8, CategoricalChip=9, ColorScale=10,
|
|
};
|
|
|
|
enum class TableEventKind : uint8_t {
|
|
ButtonClick=1, RowDoubleClick=2, RowRightClick=3, CellEdit=4,
|
|
};
|
|
|
|
struct TableEvent {
|
|
TableEventKind kind;
|
|
int row;
|
|
int col;
|
|
std::string column_id;
|
|
std::string action_id; // ColumnSpec::button_action on ButtonClick
|
|
std::string value;
|
|
};
|
|
|
|
struct State {
|
|
// (opaca al consumidor — gestionada internamente; debe persistir entre frames)
|
|
};
|
|
|
|
// Module metadata (NEW en este issue)
|
|
namespace data_table {
|
|
const char* module_version(); // ej. "2.0.0"
|
|
const char* module_description();
|
|
}
|
|
```
|
|
|
|
Esta especificacion va a `docs/MODULES_API.md` (issue 0107f) como el contrato canonico de `data_table_cpp`.
|
|
|
|
### Patron de uso canonico (apps)
|
|
|
|
```cpp
|
|
static data_table::State st;
|
|
data_table::TableInput tbl;
|
|
tbl.name = "runs";
|
|
tbl.headers = {"id", "status", "duration_ms", "started_at"};
|
|
tbl.types = {ColumnType::String, ColumnType::String, ColumnType::Float, ColumnType::Date};
|
|
tbl.cells = &cells_flat[0];
|
|
tbl.rows = N;
|
|
tbl.cols = 4;
|
|
|
|
// Configure renderers
|
|
tbl.column_specs.resize(tbl.cols);
|
|
for (int i = 0; i < tbl.cols; i++) tbl.column_specs[i].id = tbl.headers[i];
|
|
|
|
tbl.column_specs[1].renderer = data_table::CellRenderer::CategoricalChip;
|
|
tbl.column_specs[1].chips = { {"ok", "#22c55e"}, {"error", "#ef4444"}, {"running", "#f59e0b"} };
|
|
|
|
tbl.column_specs[2].renderer = data_table::CellRenderer::ColorScale;
|
|
tbl.column_specs[2].range_min = 0.0;
|
|
tbl.column_specs[2].range_max = 5000.0;
|
|
tbl.column_specs[2].range_alpha = 0.30f;
|
|
|
|
// Button column for retry action
|
|
data_table::ColumnSpec retry_col;
|
|
retry_col.id = "actions";
|
|
retry_col.renderer = data_table::CellRenderer::Button;
|
|
retry_col.button_action = "retry_run";
|
|
retry_col.button_label = "Retry";
|
|
tbl.headers.push_back("actions");
|
|
tbl.column_specs.push_back(retry_col);
|
|
|
|
// Render + consume events
|
|
std::vector<data_table::TableEvent> events;
|
|
data_table::render("##runs_tbl", {tbl}, st, &events);
|
|
|
|
for (auto& ev : events) {
|
|
if (ev.kind == data_table::TableEventKind::ButtonClick && ev.action_id == "retry_run") {
|
|
// app handler
|
|
}
|
|
if (ev.kind == data_table::TableEventKind::RowDoubleClick) {
|
|
// open detail modal
|
|
}
|
|
}
|
|
```
|
|
|
|
## Tareas
|
|
|
|
- [ ] **1.1** Investigar TODOS los patrones de uso del modulo en apps (script audit):
|
|
```bash
|
|
grep -rhnE "data_table::(render|TableInput|State|ColumnSpec|TableEvent|CellRenderer::|TableEventKind::|JoinStrategy)" \
|
|
apps/*/main.cpp apps/*/*.cpp projects/*/apps/*/*.cpp 2>/dev/null | sort -u > /tmp/data_table_api_usage.txt
|
|
```
|
|
Producir tabla de patrones canonicos en `docs/MODULES_API.md`.
|
|
- [ ] **2.1** Backup `apps/primitives_gallery/playground/tables/{data_table.cpp,data_table_logic.cpp,tql.cpp,tql_to_sql.cpp,lua_engine.cpp,llm_anthropic.cpp,viz.cpp,tql_duckdb.cpp,self_test.cpp}` a `apps/primitives_gallery/playground/tables/legacy_archive/` (gitignored o conservado por historia).
|
|
- [ ] **2.2** Editar `apps/primitives_gallery/playground/tables/CMakeLists.txt`:
|
|
- Quitar sources legacy.
|
|
- `target_link_libraries(tables_playground PRIVATE fn_module_data_table)`.
|
|
- El target `tables_playground_self_test` se renombra a `tables_playground_module_test` con sources nuevos.
|
|
- [ ] **3.1** Crear `playground.cpp` con tab bar + menu acciones.
|
|
- [ ] **3.2** Crear 9 archivos `tables/tbl_*.cpp` con cada caso demo.
|
|
- [ ] **3.3** Crear `version_compat.cpp` con selector + side-by-side.
|
|
- [ ] **3.4** Anadir API `data_table::module_version()` + `module_description()` a `modules/data_table/data_table.h`. Implementacion lee `version_generated.h` (post 0107c bump a 2.0.0).
|
|
- [ ] **4.1** Crear `test_module.cpp` headless con `imgui_test_engine` (gated por `FN_BUILD_TESTS=ON`):
|
|
- 1 test por `CellRenderer` enum.
|
|
- 1 test por `TableEventKind`.
|
|
- 1 test de joins.
|
|
- 1 test de TQL pipeline.
|
|
- Compara cells output via golden TSV.
|
|
- [ ] **4.2** Migrar lo aplicable del `self_test.cpp` legacy (430 checks) a tests del modulo. Lo que prueba logica pura ya extraida al registry (parse_number, compare, tql parsing, lua) va a `cpp/functions/core/*_test.cpp`. Lo que prueba render() va a `test_module.cpp`.
|
|
- [ ] **5.1** Actualizar `e2e_run.sh`: build + module_test + capture screenshot.
|
|
- [ ] **5.2** Integrar con `primitives_gallery --capture` (golden images de cada tab del testbed).
|
|
- [ ] **5.3** README.md con checklist de QA por tab.
|
|
- [ ] **6.1** Verificar que las apps consumidoras (7) siguen compilando y comportandose igual.
|
|
- [ ] **6.2** `fn index` para que el playground actualizado quede registrado (aunque sea playground, su `app.md` puede declarar `uses_modules: [data_table_cpp]` con min_version 2.0.0).
|
|
|
|
## Decisiones de diseño
|
|
|
|
1. **Eliminar sources legacy**: opcion alternativa era mantenerlos como golden-reference. Decision: eliminarlos. Razon: si quedan vivos, la tentacion de "arreglar el playground" sin tocar el modulo permanece. Forzar a usar el modulo cierra el loop.
|
|
2. **Side-by-side downgrade**: opcion alternativa era branchear el modulo en `data_table_v1` static lib paralelo. Decision: feature flags por version dentro del modulo actual (`v_compat_mode`). Menos duplicacion, mas pragmatico.
|
|
3. **`module_version()` como API publica**: alternativa era leer `version_generated.h` desde la app. Decision: API publica + estable. Las apps que quieran mostrar la version del modulo en su About panel lo hacen via la funcion, no via include privado.
|
|
|
|
## Prerequisitos
|
|
|
|
- Issue 0107 (estandarizacion modulos) cerrado y `modules-v2` activo. **Bloqueo duro**: sin 0107, el sistema sigue con drift y refactorizar el playground encima de codigo inestable es desperdicio.
|
|
|
|
## Riesgos
|
|
|
|
- **Headless test con imgui_test_engine**: requiere `FN_BUILD_TESTS=ON`. Si no esta disponible en CI, los tests no corren. Mitigacion: documentar requirement en CI config + fallback a tests de logica pura sin UI.
|
|
- **Version compat mode** es facil de hacer mal: el codigo del modulo se llena de `if (v_compat < X)`. Mitigacion: limite estricto — solo se mantiene compat para las 2 versiones previas (`v_current - 1`, `v_current - 2`). Mas alla, se elimina y se documenta breaking change.
|
|
- **Capture and compare** depende de fonts/DPI/driver GL → false positives en CI. Mitigacion: capture solo en Linux x86_64 con driver mesa fijado.
|
|
|
|
## Infra de testing reutilizable (existente en el repo)
|
|
|
|
El testbed se construye combinando estos mecanismos ya disponibles. Ningun nuevo motor.
|
|
|
|
| Mecanismo | Ubicacion | Que aporta al testbed |
|
|
|---|---|---|
|
|
| Catch2 unit | `cpp/tests/` (50+ tests, macro `add_fn_test`) | Tests de logica pura de sub-funciones del registry usadas por el modulo (`compute_column_stats`, `tql_emit`, `lua_engine`, `join_tables`, `tql_to_sql`). Ya existen — los tomamos como dado. |
|
|
| Golden PNG diff | `cpp/tests/golden/` (43 PNGs) + `png_diff.cpp` | Cada tab del testbed exporta golden via `primitives_gallery --capture`. Diff en CI bloquea drift visual. |
|
|
| Auto-driving worker | `apps/altsnap_jitter_test/main.cpp` (modelo de referencia) | Pattern: worker thread fakea eventos + counters monotonicos en `fn::internal::*` + `set_force_alt_for_test()` bypass. Replicar para `data_table`: counters `fn::internal::data_table::{button_click_count, row_double_click_count, color_rule_applied_count, ...}` + worker que inyecta clicks. |
|
|
| Dear ImGui Test Engine | `cpp/framework/app_base.h:205-225` (`fn::run_app_test`, gated por `FN_BUILD_TESTS=ON`) | UI-level tests: `IM_REGISTER_TEST` lambdas que llaman `ctx->ItemClick`, `ctx->ItemDoubleClick`, verifican events emitidos. **CERO consumidores actuales** — el testbed sera el primer cliente. |
|
|
| `--self-test` flag | `.claude/rules/e2e_validation.md` (patron canonico) | `tables_playground --self-test` corre toda la suite headless, exit 0/1. Compatible con CI sin display. |
|
|
| Cross-platform e2e | `bash/functions/infra/e2e_run_cpp_windows.sh` | Funcion bash: cross-compile mingw + deploy Desktop + taskkill + run native + capturar stdout/exit. Linux CI valida Windows. |
|
|
|
|
### Panel de control QA agresivo (UI del testbed)
|
|
|
|
Barra superior con:
|
|
|
|
1. **Toggles por feature** (15+ checkboxes). Cada toggle muta `ColumnSpec` o `State` en runtime, mismo patron que apps reales hacen en codigo de setup. El usuario QA puede activar/desactivar y ver cambio visual inmediato sin recompilar.
|
|
2. **Inyector de eventos**: 4 botones — "Simulate ButtonClick", "Simulate RowDoubleClick", "Simulate RowRightClick", "Simulate Drill". Cada uno llama el worker thread pattern de altsnap → verifica que `TableEvent` apropiado se emite.
|
|
3. **Counters live**: contador por evento, contador por renderer aplicado, latency p50/p95 por frame (medir desde `ImGui::GetIO().Framerate`). Visible siempre, util para detectar regresiones de performance.
|
|
4. **Version selector**: dropdown con versiones del modulo (`2.0.0` actual, `1.4.0` compat, `1.3.0` compat...). Cambio re-renderiza la misma data side-by-side: actual vs version seleccionada. Diff visual inmediato + diff de events emitidos.
|
|
5. **Boton "Run --self-test"**: ejecuta toda la suite headless dentro de la misma ventana. Output en log panel. Util para iteracion rapida sin relanzar.
|
|
6. **Boton "Export golden"**: corre `--capture` sobre cada tab, escribe PNGs a `golden/data_table_testbed/`. Para regenerar baseline tras cambio visual intencional.
|
|
|
|
### Counters internos a anadir al modulo
|
|
|
|
Issue 0108 anade en `modules/data_table/data_table.cpp`:
|
|
|
|
```cpp
|
|
namespace data_table::internal {
|
|
int button_click_count();
|
|
int row_double_click_count();
|
|
int row_right_click_count();
|
|
int color_rule_applied_count();
|
|
int tql_stages_executed();
|
|
int last_render_duration_us();
|
|
void reset_counters();
|
|
}
|
|
```
|
|
|
|
Mismo modelo que `fn::internal::*` para altsnap. Test-only observability, cost cero en prod (counters atomicos triviales).
|
|
|
|
## Notas
|
|
|
|
- Issue 0108 NO empieza hasta 0107 cerrar. Bloqueado duro.
|
|
- Se referencia desde `docs/MODULES_API.md` (0107f) como el ejemplo canonico de "como usar el modulo data_table".
|
|
- Cuando 0108 cierre, abrir issue 0109 paralelo para `chat_ia` (que era el siguiente modulo planeado al inicio de 0107).
|