tawasuyu

tawasuyu/brahman

Rust 467TOML 184Markdown 49JSON 176411 Definiciones

feat(cosmobiologia): rectificador automático — UI de entrada y disparo

Segundo incremento: el rectificador ya es usable de punta a punta
desde el panel, sin infraestructura de UI nueva.

- cosmobiologia-panel: Control::TextInput pasa a renderizarse desde
  string_state — deja de ser un display estático y se vuelve un campo
  de sólo-lectura que el shell escribe vía set_string (resultados,
  etiquetas).
- cosmobiologia-modules: el módulo primary_directions gana 3 sliders
  «Evento N · edad» (0 = ranura sin usar), un Action «Rectificar
  hora» y un TextInput «Resultado».
- shell: run_rectificacion lee las edades de los sliders, llama a
  engine::rectificar (ventana ±15 min, paso 1) y escribe la hora
  rectificada + el puntaje en el campo Resultado del panel.

El rectificador queda funcional: activar GR → fijar edades de eventos
→ «Rectificar hora» → leer el resultado. Falta sólo la curva del
perfil del barrido como visualización (incremento opcional).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

a7e9662fad6374d68ea058b126648baa347fa778 · por sergio · (hace 5 meses) · padre 0ada1050f · ver el árbol en este commit

Delta semántico · 5

crates/apps/cosmobiologia/src/shell.rs

crates/modules/cosmobiologia/cosmobiologia-modules/src/lib.rs

crates/modules/cosmobiologia/cosmobiologia-panel/src/lib.rs

3 archivos · +111 −17

diff --git a/crates/apps/cosmobiologia/src/shell.rs b/crates/apps/cosmobiologia/src/shell.rs
index ddb78b1..cc40dea 100644
--- a/crates/apps/cosmobiologia/src/shell.rs
+++ b/crates/apps/cosmobiologia/src/shell.rs
@@ -32,8 +32,8 @@ use cosmobiologia_canvas::{
     AstrologyCanvas, CanvasEvent, CanvasMode, ThumbnailItem, ThumbnailScope,
 };
 use cosmobiologia_engine::{
-    LayerKind, NatalOptions, OUTER_RING_MODULES, PipelineRequest, compose_with_options,
-    svg_export,
+    EventoConocido, LayerKind, NatalOptions, OUTER_RING_MODULES, PipelineRequest,
+    compose_with_options, svg_export,
 };
 use cosmobiologia_model::{
     Chart, ChartId, ChartKind, ContactId, FreeChartId, ModuleState, StoredBirthData,
@@ -1365,24 +1365,64 @@ impl Shell {
     /// Otros módulos overlay (progression, solar_arc, primary_directions)
     /// son extensión natural — TODO.
     fn on_panel_action(&mut self, module_id: String, key: String, cx: &mut Context<Self>) {
-        if key != "save_as_free" {
-            return;
-        }
-        match module_id.as_str() {
-            "planetary_return" => self.save_planetary_return_as_free(cx),
-            "transit" => self.save_transit_as_free(cx),
-            "progression" => self.save_progression_as_free(cx),
-            // Solar arc y direcciones primarias son transformaciones
-            // matemáticas puras (no tienen un birth_data real
-            // equivalente — un Chart natal computado en el "momento
-            // SA" daría posiciones distintas a las dirigidas). Para
-            // guardarlas haría falta extender Chart con un kind
-            // `Derived { source, transform, params }` que el engine
-            // sepa rehidratar. TODO.
+        match key.as_str() {
+            "save_as_free" => match module_id.as_str() {
+                "planetary_return" => self.save_planetary_return_as_free(cx),
+                "transit" => self.save_transit_as_free(cx),
+                "progression" => self.save_progression_as_free(cx),
+                // Solar arc y direcciones primarias son transformaciones
+                // matemáticas puras (no tienen un birth_data real
+                // equivalente). Guardarlas exigiría un `ChartKind`
+                // `Derived { source, transform, params }`. TODO.
+                _ => {}
+            },
+            "rectificar" => self.run_rectificacion(cx),
             _ => {}
         }
     }
 
+    /// Lanza el rectificador automático (Sistema GR): lee las edades de
+    /// los eventos conocidos de los sliders del módulo, barre las horas
+    /// candidatas y escribe el resultado en el campo «Resultado» del
+    /// panel. El barrido es síncrono — para ±15 min son ~31 cartas.
+    fn run_rectificacion(&mut self, cx: &mut Context<Self>) {
+        // Clonamos la carta: `rectificar` necesita `&Chart` y luego
+        // `panel.update` toma `&mut self` — no pueden solaparse.
+        let Some(chart) = self.current_chart.clone() else {
+            return;
+        };
+        let cfg = self.module_configs.get("primary_directions");
+        let read_age = |key: &str| -> f64 {
+            cfg.and_then(|c| c.get(key))
+                .and_then(|v| v.as_f64())
+                .unwrap_or(0.0)
+        };
+        // Edades > 0 — una ranura en 0 es "sin usar".
+        let eventos: Vec<EventoConocido> = ["evento_1", "evento_2", "evento_3"]
+            .iter()
+            .map(|k| read_age(k))
+            .filter(|edad| *edad > 0.5)
+            .map(|edad| EventoConocido { edad_years: edad })
+            .collect();
+        let key_gr = cfg
+            .and_then(|c| c.get("key"))
+            .and_then(|v| v.as_str())
+            .unwrap_or("naibod")
+            .to_string();
+
+        // Ventana ±15 min, paso 1 min — el barrido GR estándar.
+        let resultado = match cosmobiologia_engine::rectificar(&chart, &eventos, 15, 1, &key_gr) {
+            Ok(r) => format!(
+                "{:+} min · puntaje {:.2}",
+                r.mejor_offset_minutos, r.mejor_puntaje
+            ),
+            Err(_) => "define al menos un evento (edad > 0)".to_string(),
+        };
+        self.panel.update(cx, |p, cx| {
+            p.set_string("primary_directions", "resultado", Some(resultado), cx)
+        });
+    }
+
     /// Snapshot del cielo en este instante anclado al lugar del
     /// natal. Sufijo `transito-{fecha}`. Útil para guardar "qué
     /// estaba pasando ahora en la carta de Pedro".
diff --git a/crates/modules/cosmobiologia/cosmobiologia-modules/src/lib.rs b/crates/modules/cosmobiologia/cosmobiologia-modules/src/lib.rs
index 5fe6348..33e43ea 100644
--- a/crates/modules/cosmobiologia/cosmobiologia-modules/src/lib.rs
+++ b/crates/modules/cosmobiologia/cosmobiologia-modules/src/lib.rs
@@ -947,6 +947,43 @@ pub mod primary_directions {
                         },
                     ],
                 },
+                // --- Rectificador automático ---
+                // Tres edades de eventos conocidos de la vida del
+                // sujeto; `0` = ranura sin usar. El barrido GR busca la
+                // hora de nacimiento que mejor las explica.
+                Control::Slider {
+                    key: "evento_1".into(),
+                    label: "Evento 1 · edad".into(),
+                    min: 0.0,
+                    max: 90.0,
+                    step: 1.0,
+                    default: 0.0,
+                },
+                Control::Slider {
+                    key: "evento_2".into(),
+                    label: "Evento 2 · edad".into(),
+                    min: 0.0,
+                    max: 90.0,
+                    step: 1.0,
+                    default: 0.0,
+                },
+                Control::Slider {
+                    key: "evento_3".into(),
+                    label: "Evento 3 · edad".into(),
+                    min: 0.0,
+                    max: 90.0,
+                    step: 1.0,
+                    default: 0.0,
+                },
+                Control::Action {
+                    key: "rectificar".into(),
+                    label: "Rectificar hora".into(),
+                },
+                Control::TextInput {
+                    key: "resultado".into(),
+                    label: "Resultado".into(),
+                    default: "—".into(),
+                },
             ]
         }
         fn compute_layers(&self, _chart: &Chart, _cfg: &serde_json::Value) -> Vec<Layer> {
diff --git a/crates/modules/cosmobiologia/cosmobiologia-panel/src/lib.rs b/crates/modules/cosmobiologia/cosmobiologia-panel/src/lib.rs
index a752685..cb64811 100644
--- a/crates/modules/cosmobiologia/cosmobiologia-panel/src/lib.rs
+++ b/crates/modules/cosmobiologia/cosmobiologia-panel/src/lib.rs
@@ -173,6 +173,14 @@ impl ControlPanel {
                                     .entry((m.id().to_string(), key))
                                     .or_insert(Some(default));
                             }
+                            // `TextInput` es un campo de sólo-display que el
+                            // shell escribe (resultados, etiquetas) vía
+                            // `set_string`; su estado vive en `string_state`.
+                            Control::TextInput { key, default, .. } => {
+                                self.string_state
+                                    .entry((m.id().to_string(), key))
+                                    .or_insert(Some(default));
+                            }
                             _ => {}
                         }
                     }
@@ -550,7 +558,16 @@ impl ControlPanel {
                 options,
                 default,
             } => self.render_select(theme, module_id, key, label, options, default, cx),
-            Control::TextInput { label, default, .. } => display_row(theme, label, default),
+            Control::TextInput { key, label, default } => {
+                // Sólo-display: muestra lo último que el shell escribió
+                // con `set_string`, o el `default` si nada se escribió.
+                let valor = self
+                    .string_state
+                    .get(&(module_id.to_string(), key.to_string()))
+                    .and_then(|o| o.clone())
+                    .unwrap_or_else(|| default.clone());
+                display_row(theme, label, &valor)
+            }
             Control::Action { key, label } => {
                 self.render_action(theme, module_id, key, label, cx)
             }