feat(tahuantinsuyu): "Guardar como…" en módulo Retorno planetario (F4)
Cierra la fase B con el botón pedido por el usuario: tener una
carta natal abierta, activar el módulo Retorno planetario con
edad N + cuerpo (ej. Sol, 34 años), y al click guardar la carta
resultante con sufijo automático `rs-34` en el mismo contacto.
Infraestructura nueva (extensible a otros overlays):
- `Control::Action { key, label }` en tahuantinsuyu-modules —
un botón sin estado que el panel pinta como pill clickeable.
- `PanelEvent::Action { module_id, key }` que el panel emite
al click y el shell despacha.
- `render_action` en tahuantinsuyu-panel: pill con bg_button
+ hover + border. Wrap en Div plano para tipo coherente.
Backend (eternal-bridge):
- Nueva función pública `compute_planetary_return_chart(chart,
body, target_age_years, shift_days) -> (StoredBirthData,
instant_label)` en `tahuantinsuyu-engine`. Reusa el cómputo
ya existente del overlay: `next_return` + parser ISO-8601
para extraer year/mm/dd/hh:mm:ss del instant del retorno.
Hereda lat/lon/alt/TZ del natal — convención clásica del
Solar return en la ciudad de nacimiento.
Flujo en el shell:
- Handler `on_panel_action` despacha por `(module_id, key)`. Hoy
solo `planetary_return.save_as_free` está cableado; otros
módulos overlay (progression, solar_arc, primary_directions,
transit) son extensión natural — TODO.
- `save_planetary_return_as_free`:
1) lee config (body, age, shift_days) del module_configs
2) llama `compute_planetary_return_chart`
3) construye un `Chart` clonando el natal con birth_data
nuevo + label `{contacto} rs-34 · 2024-08-12 14:23 UTC`
(sufijo según cuerpo: `rs` para Sol, `lunar` para Luna,
nombre directo para los demás)
4) inserta como FreeChart con id `free-{N}` y la
selecciona para que el usuario la vea
- El usuario después puede usar el menú contextual de la
free chart para "Guardar como…" → modal F3 → persiste
bajo el contacto que elija (típicamente el del natal).
UX completa:
1. Tener natal abierta
2. Panel: módulo "Retorno planetario" → Activar + elegir
cuerpo + slider edad
3. Click "💾 Guardar retorno como carta libre"
4. La nueva carta aparece en "Cartas libres" seleccionada
5. Click derecho → "Guardar como…" → elegir contacto +
confirmar nombre
10 tests verdes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1300,6 +1300,83 @@ impl Shell {
|
||||
// Fase 7: encender/apagar módulos enteros desde un
|
||||
// header con switch (vs. el toggle por-control de hoy).
|
||||
}
|
||||
PanelEvent::Action { module_id, key } => {
|
||||
self.on_panel_action(module_id.clone(), key.clone(), cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Click sobre un `Control::Action` del panel. Por ahora maneja:
|
||||
/// - planetary_return.save_as_free → captura la carta del
|
||||
/// retorno actual (cuerpo + edad) como FreeChart con sufijo
|
||||
/// `rs-{N}` / `lunar-{N}` / etc. según el cuerpo elegido.
|
||||
///
|
||||
/// 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 module_id == "planetary_return" && key == "save_as_free" {
|
||||
self.save_planetary_return_as_free(cx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Computa la carta del retorno planetario actual (con cuerpo +
|
||||
/// edad del módulo) y la inserta como FreeChart. El usuario
|
||||
/// puede después "Guardar como…" para persistirla bajo un
|
||||
/// contacto (típicamente el mismo del natal).
|
||||
fn save_planetary_return_as_free(&mut self, cx: &mut Context<Self>) {
|
||||
let Some(natal) = self.current_chart.as_ref() else {
|
||||
eprintln!("[shell] save_planetary_return: sin carta activa");
|
||||
return;
|
||||
};
|
||||
if natal.id == ChartId::default() {
|
||||
eprintln!("[shell] save_planetary_return: la carta activa es libre, no natal");
|
||||
return;
|
||||
}
|
||||
let cfg = self
|
||||
.module_configs
|
||||
.get("planetary_return")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::json!({}));
|
||||
let body = cfg
|
||||
.get("body")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("sun")
|
||||
.to_string();
|
||||
let age = self.module_age_or_current("planetary_return");
|
||||
let shift_days = cfg
|
||||
.get("shift_days")
|
||||
.and_then(|v| v.as_f64())
|
||||
.unwrap_or(0.0) as i64;
|
||||
|
||||
// Pedimos al engine la fecha exacta del retorno. La engine
|
||||
// expone `compute_planetary_return_chart` que devuelve un
|
||||
// `StoredBirthData` listo para reusar como carta natal.
|
||||
match tahuantinsuyu_engine::compute_planetary_return_chart(
|
||||
natal, &body, age, shift_days,
|
||||
) {
|
||||
Ok((birth, instant_label)) => {
|
||||
let suffix = match body.as_str() {
|
||||
"sun" => "rs",
|
||||
"moon" => "lunar",
|
||||
other => other,
|
||||
};
|
||||
let label = format!(
|
||||
"{} {}-{:.0}a · {}",
|
||||
natal.label, suffix, age, instant_label
|
||||
);
|
||||
let id = FreeChartId(format!("free-{}", self.next_free_id));
|
||||
self.next_free_id += 1;
|
||||
let mut chart = natal.clone();
|
||||
chart.id = ChartId::default();
|
||||
chart.label = label;
|
||||
chart.birth_data = birth;
|
||||
self.free_charts.insert(id.clone(), chart);
|
||||
self.push_free_charts_to_tree(cx);
|
||||
self.apply_selection(TreeSelection::FreeChart(id), cx);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[shell] compute_planetary_return_chart: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user