NightScribe — Flujos guiados (UX v3)
Documento maestro del rediseño centrado en proyectos. Decisiones: ADR-019 (UX de proyectos), ADR-020 (horizonte y restricciones), ADR-021 (exportación de secuencias y efemérides), ADR-022 (reporte MPC). Este documento describe qué se construye y en qué orden; no hay código todavía (2026-08-24).
1. La pregunta del observador
«¿Qué puedo hacer esta noche — o ahora mismo?». La app responde sugiriendo los objetivos más interesantes bajo las restricciones reales del observatorio, y de la elección nace un proyecto que guía todo lo demás sin volver a preguntar nada.
2. Restricciones que el planificador debe respetar
| Restricción | Origen | Efecto |
|---|---|---|
| Magnitud límite | limit_mag en config (equipo) | Híbrida (ADR-025): duro en SN/cometas/exoplanetas, aviso ⚠ mag>N en NEO/PCCP sin descartar |
| Horizonte local | Fichero TheSkyX del usuario (ADR-020) | Altura mínima por azimut + margen de seguridad; fallback a min_alt plano |
| Luna | ephem_minor (ADR-009) | Aviso + penalización de score por separación/iluminación; no filtro duro |
| Viabilidad de sesión | core/exposure.py + horizonte | La ventana debe cubrir la duración de la sesión; se muestra «inicio seguro hasta HH:MM» |
| Escala de placa | Perfil de cámara (pixel_um, focal_mm) | Exposición máxima sin traza en NEOs |
3. Anatomía de un proyecto
Entidad persistente (ADR-019): tipo, objeto, estado (active/done/archived), contexto JSON completo y tres pasos guiados. El contexto se captura al crear el proyecto desde Esta noche (snapshot del objetivo: coords, mag, rate, ventana…) y se enriquece en cada paso (secuencia exportada, FITS importado, medidas, posts).
Pasos: Ficha → Captura → Análisis → Publicación (ADR-045, 2026-09-24: «Procesado» se renombra «Análisis» con migración v8 y la pestaña Seguimiento se disuelve en ella; la pestaña Análisis se construye alrededor del gestor de visitas para todos los tipos: cada día que se trabaja el objeto es una visita y de ella cuelgan sus recursos — placas, reportes, importaciones — registrados en project_files con session_id; nada se adjunta sin visita. Revisión 2026-09-06 de ADR-019: el paso Captura se fundió en el Plan y gana el control real de CCDciel — ADR-030). El cómo de medir una serie desde la visita (fotometría de secuencias, detrend, multinoche, ExoClock y modo en vivo) está en SEQUENCES.es.md; el cierre científico de un tránsito pasa por orquestar EXOTIC desde la app (ver SEQUENCES y ADR-052).
4. Flujo — supernova / transitorio
Plan: tarjeta con magnitud actual, edad, galaxia anfitriona, ventana sobre el horizonte real y «inicio seguro hasta HH:MM». El usuario define N tomas × exposición y filtro; la app comprueba que la sesión termina dentro del rango de seguridad.
Captura: exportar secuencia (NINA JSON / CCDciel / CSV) con nombre, coords J2000 y tiempos; el fichero queda registrado en el proyecto. Con el observatorio conectado (ADR-030, ADR-081: CCDciel o NINA/PINS) también se puede: apuntar el telescopio, hacer un ajuste astrométrico (plate solve + corrección), detener el movimiento (seguridad), enviar el plan (objeto, exposición, nº de tomas, filtro) e iniciar la captura desde la misma pestaña. La lista de filtros del plan es la real de la rueda; el snapshot de NINA toma una exposición, así que su nº de tomas es solo informativo.
Procesado (externo): el usuario calibra/apila con sus programas; al volver, «Importar FITS resultado» — sin preguntar objeto ni coordenadas (ya están en el contexto) — y confirma con el blink, casado con PS1-g (pipeline ADR-018 pre-rellenado), y exporta GIF/MP4/PNG con el watermark del observatorio.
Publicar: historia ES/EN + tuit con los datos reales de la sesión (fecha, N×t, filtro) + marca observado/posteado en el historial.
5. Flujo — NEO / candidato PCCP
Plan: tarjeta con velocidad aparente (″/min) y exposición máxima sin traza calculada con la escala de placa del equipo; número de tomas; ventana e «inicio seguro». Aviso si la sesión terminaría fuera de seguridad.
Captura: exportar efemérides a paso configurable para TheSkyX y Cartes du Ciel (formatos validados con importación real en la fase 5; CSV como base) + secuencia para el software de captura. Con CCDciel conectado (ADR-030): enviar el plan e iniciar la captura en vivo.
Procesado: el usuario mide fuera (Astrometrica u otro) y pega las medidas en el proyecto → validación (80-col/ADES, código MPC, designación) → fichero empaquetado listo para enviar al MPC (ADR-022). El envío lo hace el usuario.
Publicar: historia ES/EN +
orbit_view/sky_view+ observado/posteado + report a NEOfixer si hay clave configurada. La interpretación orbital (familia, MOID, tamaño desde H, próxima aproximación — reutilizaenrich.py+orbits.py) no es un paso: ya se pinta en la pestaña Detalles al abrir el proyecto.
6. Flujos posteriores
Cometas y tránsitos de exoplanetas reutilizan el mismo esqueleto de 3 pasos cuando los flujos de SN y NEO estén asentados. Fuera de este rediseño inicial.
7. Hoja de ruta y punto de entrada
| Fase | Entregable | Estado |
|---|---|---|
| 1 | ADR-019…022 + este documento + DESIGN actualizado | Hecho (2026-08-24) |
| 2 | Fundación de restricciones: core/horizon.py, Luna, core/exposure.py, perfil de cámara en config, integración en planner/suggest, silueta en sky_view, tests | Hecho (2026-08-24) |
| 3 | Modelo de proyecto: migración db (user_version 0→1), core/project.py, máquina de pasos, tests | Hecho (2026-08-24) |
| 4 | GUI v3: 4 pestañas, hub Proyectos con stepper por tipo, blink contextual, tarjetas de Esta noche con «Crear proyecto» e «inicio seguro», Settings (Cámara/Horizonte/Sesión/Luna), i18n | Hecho (2026-08-24) |
| 5 | Exportadores: core/sequence.py (NINA/CCDciel/CSV) + efemérides (CSV + TheSkyX + CdC validados en importación real), tests | Hecho (2026-08-24); CCDciel con formato real .targets (CONFIG v5) contra docs/ccdciel_sequence_sample.targets (2026-09-06) |
| 6 | core/mpc_report.py + paso en flujo NEO + subcomando CLI project mínimo + polish, i18n y docs finales | Hecho (2026-08-24) |
7bis. Rediseño de la pantalla de inicio (2026-08-25)
Motivación: la pantalla «Esta noche» no reflejaba la estética de la app, no explicaba por qué se sugiere cada objeto (la frase «¿por qué esta noche?» solo vivía en el tooltip) y el layout de mini-tarjetas en 4×2 se leía apretado. Además la app no tenía tema global: tarjetas oscuras flotando sobre cromo nativo claro.
| Fase | Entregable | Estado |
|---|---|---|
| A | Tema global oscuro: gui/theme.py (paleta + QSS cromo + KIND_COLORS), apply_theme(app) en app.py, ADR-026, tests/unit/test_theme.py | Hecho (2026-08-25) |
| B | «Esta noche» como lista vertical de filas amplias: nombre, «¿por qué?» visible, chips (mag/alt/ventana/luna/⚠), mini-barra de 4 segmentos del score, score 0-100, botón Start/Continue, podio metálico top-3, click-fila→Explorar, estados loading/vacío | Hecho (2026-08-25) |
| C | Tabla «Lista completa» rediseñada sobre el tema, doble-click→proyecto, regeneración i18n ES/EN de las cadenas nuevas, tests de humo | Hecho (2026-08-26) |
Fase C (hecha, 2026-08-26): la tabla se conserva y ahora se comporta como las filas amplias de encima — selección por fila (sin celdas 2×2, sin números de fila), cada fila con un matiz de su tipo de objeto, el top 3 (orden de score) con el matiz más marcado como un podio discreto, nombre y score en negrita con el color del tipo, cabecera/selección/hover del tema global (ADR-026). El doble-click desde cualquier columna crea/continúa el proyecto. Código en nightscribe/gui/main_window.py (_prepare_table, _fill_table, _table_start_project) + tonight_tab.ui (tooltip). Cadenas nuevas traducidas ES/EN y .ts/.qm regenerados (ADR-014). Tests de humo en tests/unit/test_tonight_table.py (patrón de test_tonight_rows.py).
7ter. Fase D — La vista de objeto como protagonista del hub de Proyectos (2026-08-26)
Motivación: al abrir un proyecto lo primero que se ve es el Plan (tomas × exposición), pero sin objeto no hay plan. La «carta de presentación» del objeto — hoy solo dentro del diálogo modal Explore…— debe ser lo primero y siempre visible. No hay que construir nada nuevo: toda la funcionalidad ya existe (enrich, narrativa, órbitas, gráficos, exposición); toca unificarla en un panel fijo y colocarla de forma atractiva e intuitiva, sin texto cortado (multilínea, todo visible).
Decisiones pactadas (2026-08-26):
La vista de objeto es un panel fijo por encima de las pestañas de pasos (Plan/Captura/Procesado/Publicar) en el hub de Proyectos. No es un paso: no se salta y no se «marca hecho»; no entra en la máquina de pasos. (Revisión 2026-08-27: en cambio es la pestaña «Detalles», la primera y la que queda abierta al seleccionar el proyecto — misma garantía, el objeto siempre visible, pero ocupando toda la altura del panel. Revisión de nuevo 2026-08-28 (ADR-019): la lista de pasos pasa a ser de 4 — ver revisión de ADR-019.)
Incluye (todo ya existe): frase de enganche + bullets divulgativos + tabla de parámetros con explicación multilínea ES/EN + los gráficos (órbita, cielo, campo, curva de luz — se ocultan los que no se pueden generar; si no hay ninguno, desaparece también el grupo) + bloques de captura/ventana (mag, tasa ″/min, exposición máx. sin traza si aplica, ventana inicio–fin, horas arriba).
Se extrae a
gui/overview.pyun panel compartido (ObjectPanel) que usan el
hub de Proyectos y el diálogo Explore… del menú Herramientas: una sola fuente de verdad.
No se toca
core/completo, la máquina de pasos (core/project.py,_STEP_KEYS) ni el panel Plan (su calculadora de exposición máxima sigue ahí).
Piezas reutilizadas sin cambios (con su hogar actual):
| Pieza | Origen |
|---|---|
| Enrich (red, caché) | gui/workers.py ExploreWorker → core/enrich |
| Frase de enganche, bullets | core/narrative.hook, core/narrative.fact_bullets |
| Parámetros + explicación ES/EN | core/orbits.explain_elements, core/orbits.explain_neofixer |
| PNGs (órbita/cielo/campo/curva de luz) | core/post.build_charts (core/post.py:172) |
| Exposición máx. sin traza (NEO) | core/exposure.plate_scale + max_exposure_no_trail |
| Mag, tasa, ventana, horas | project.context (snapshot, gui/main_window.py:1474) |
| Clic en gráfico → zoom/export | gui/main_window.py:74 _ChartClickFilter |
| Idioma de pares ES/EN | orbits.pick (patrón MainWindow._txt) |
Estado actual que se reorganiza: la presentación de objeto vive hoy solo en el diálogo modal gui/main_window.py:1504 (_open_explore_dialog, _dialog_explore_params:1551, _dialog_explore_charts:1588, _render_object_charts:1572), que se abre al pulsar el paso 4 Análisis (_build_analyse_tab:1197). El hub de Proyectos muestra primero el Plan y una cabecera lbl_context de una sola línea truncada (gui/ui/projects_tab.ui:51).
| Fase | Entregable | Criterio de aceptación | Estado |
|---|---|---|---|
| D1 | gui/overview.py: ObjectPanel sin gráficos — estados cargando / no encontrado / listo, frase de enganche, bullets, tabla de parámetros con columna de explicación amplia (multilínea). show(e, ctx=None) + loader inyectable (offscreen sin red). Cabecera GPL, pares ES/EN | Panel construido offscreen; con e fake: hook visible, filas de parámetros con explicación; con {}: estado no encontrado | Hecho (2026-08-26) — gui/overview.py + tests/unit/test_overview_panel.py (9 tests offscreen, sin red) |
| D2 | ObjectPanel con rejilla 2×2 de gráficos rellenos por core/post.build_charts; mensajes «por qué no» (hiperbólica / sin elementos / no confirmado) escalados al widget | Con e fake: 4 slots de gráfico o los mensajes de ausencia correctos; sin e: vacíos | Hecho (2026-08-26) — rejilla orbit/sky/families/field en gui/overview.py + 5 tests offscreen en tests/unit/test_overview_panel.py (14 en total, sin red; solo field tocaría red y queda en línea «por qué no») → reviso 2026-08-27 — sin familias (ver abajo), slots orbit/sky/field/transit, tamaño panel (1200×675); lo que no se genera se omite en vez de línea «por qué no»; sin gráfico, el grupo desaparece → 2026-09-06 — los gráficos se agrupan en pestañas (QTabWidget en gui/overview.py, una por gráfico y título; con un solo gráfico la barra se auto-oculta y queda solo; adiós a la rejilla 2×2) |
| D3 | ObjectPanel con bloques de captura/ventana desde ctx: mag, tasa ″/min, exposición máx. sin traza (solo neo/pccp con tasa + pixel_um/focal_mm), ventana inicio–fin + horas arriba; se omite lo que falta | Con ctx fake de neo: chips completos; de sn: sin tasa/exposición; no rompe con ctx vacío | Hecho (2026-08-26) — fila de chips en gui/overview.py (mag / tasa / exposición máx. sin traza / ventana / horas, desde project.context; tasa y exposición solo neo/pccp) + 5 tests offscreen en tests/unit/test_overview_panel.py (19 en total, sin red). Cadenas nuevas por self.tr() quedan para i18n de la fase D6 (patrón de D1/D2) |
| D4 | Integración en el hub: el hub inserta ObjectPanel entre lbl_context y tabs_steps (conservando la cabecera lbl_context). Al seleccionar proyecto → loader = ExploreWorker (mismo fallback_target de contexto), worker bajo _keep, cancel del worker en vuelo al cambiar de proyecto | Sin red: seleccionar proyecto muestra estado cargando y no revienta; la máquina de pasos, plan y botones siguen intactos; tests de humo offscreen | Hecho (2026-08-26) — gui/main_window.py construye ObjectPanel perezosamente en _get_proj_panel() (envuelto en QScrollArea, insertado antes de tabs_steps); _project_selected lo carga con ctx + fallback_target y cancel() al cambiar de proyecto (resultado tardío no cae sobre el siguiente) + tests/unit/test_projects_hub.py (6 tests offscreen, sin red) → reviso 2026-08-27 — el panel vive ahora dentro de la pestaña Detalles (índice 0, nueva en projects_tab.ui), que es la que queda abierta al seleccionar el proyecto; prev/skip/done se desactivan ahí y next entra al paso 1 → 2026-09-06 — la lista del hub está siempre al día sin «Actualizar»: on_refresh_projects() se agenda con QTimer.singleShot(0,…) al abrir la app y se relanza en cada visita a la pestaña Proyectos (_on_main_tab_changed, tabs.currentChanged → índice 1); el botón Refresh queda como respaldo; el proyecto seleccionado se conserva al refrescar (se re-selecciona su id tras re-renderizar); +3 tests offscreen en tests/unit/test_projects_hub.py (carga al arranque, lista sin botón, se conserva la selección) |
| D5 | **Diálogo Explore… sobre el panel compartido**: main_window.py:1504 pasa a ser ObjectPanel + botón Hacer post; los _dialog_explore_* se delgan al panel; se conserva clic→zoom (el panel expone sus labels de gráfico) y _project_explore(paso 4) / _tools_explore | El diálogo Explore… del menú Herramientas idéntico en contenido; el paso 4 de NEO sigue abriéndolo pre-rellenado | Hecho (2026-08-26) — _open_explore_dialog ahora construye ObjectPanel(for_post=True) en una QScrollArea + botón Create post (señal post_requested(name, fallback) del panel → _open_post_dialog + cerrar diálogo); se eliminan _dialog_explore*/_orbit_rows/_ChartClickFilter/_set_scaled_pixmap (viven ya en overview.py); _render_object_charts queda solo para el flujo de post; explore_tab.ui huérfano se elimina; tests: 6 nuevos offscreen en test_overview_panel.py (botón post visible/oculto, señal post_requested con nombre + fallback, ignore en vuelo, cancel limpia) + 2 en test_projects_hub.py (integración _explore_panel) + 2 tests funcionales re-dirigidos al panel (orbit chart parábola + NEO no confirmado) |
| D6 | i18n + verificación + docs: lupdate/lrelease → nightscribe_{es,en}.ts/.qm (ADR-014); pytest tests/unit verde; esta sección con estados finales | Cadenas nuevas traducidas ES/EN; tests de humo en tests/unit/test_overview_panel.py (D1-D3) + humo del hub (D4) en verde | Hecho (2026-08-26) — pyside6-lupdate (fuentes */gui/*.py + */gui/ui/*.ui) refresca los 2 .ts (+17 cadenas ObjectPanel, −21 obsoletas ExploreTab); 17 cadenas nuevas completadas manualmente ES/EN (EN passthrough, ES traducido); pyside6-lrelease → 2 .qm (306 strings, 0 unfinished); smoke i18n: test_panel_strings_resolve_in_{spanish,english} montan cada .qm, verifican btn_post / grp_params / grp_charts / headers de tabla / tooltips de chips → 234 tests en verde (sin red) |
Revisión (2026-08-27) — «Detalles» como protagonista, gráficos más limpios:
Pestaña «Detalles» primero: nueva en
projects_tab.ui(índice 0 detabs_steps). La carta de presentación del objeto —ObjectPanel— vive dentro de ella (_get_proj_panella dobla allí) y es la pestaña que queda abierta al seleccionar un proyecto: toda la información del objeto a primera vista. El paso actual sigue marcado ● en su pestaña y se llega con Next →; prev / Skip / Mark done se desactivan sobre «Detalles» (no hay paso que actuar), Next sigue permitido (entra en Plan).Adiós al gráfico «¿Dónde vive?»:
viz/families_view.pyeliminado (ya no tenía consumidores);core/post.build_chartsdeja de dibujarlo yCHART_LABELSpierde la entradafamilies. En el panel el slot pasa a ser orbital / cielo / campo / curva de luz (transitos de exoplanetas, ya cubierto portransit_view).Se omiten los huecos: los slots que
build_chartsno puede generar se ocultan en vez de mostrar una línea «por qué no»; si no se genera ningún gráfico, el grupo completo desaparece. Los gráficos del panel usan el tamañopanel(1200×675, añadido astyle.SIZES) para menos margen y más legibilidad en la ventana.
Ajuste de resolución de gráficos:
Ajustes > Gráficos del panelañade la opción Al redimensionar la ventana — Rápido (el gráfico se dibuja una vez al tamañopanely se re-escala, el predeterminado) o Más nitidez (se redibuja a 2×, 2400×1350, para que los paneles grandes queden nítidos). Se guarda comoconfig["chart_zoom"]("scale"/"re-render") yObjectPanello lee antes de cada llamada abuild_charts; cambiar el ajustes con un panel en pantalla lo redibuja (rebuild_charts). (Retirado 2026-09-04: la opciónchart_zoomy la pestaña Ajustes > Gráficos se eliminaron cuando llegaron los widgets vectoriales de ADR-029, que dibujan a resolución nativa. Verdocs/adr/ADR-028y el plandocs/PLANS/approach-chart.md, Slice 0.)Visor de gráficos abierto ajustado: al hacer click sobre un gráfico,
ChartViewer se abre ajustado a la ventana — el diálogo adopta el aspecto de la imagen para que la vista por defecto la llene sin scrollbars (rueda para zoom / arrastre para desplazar / 1:1 sin cambios). El último tamaño de ventana que dejó el usuario se recuerda por gráfico (config["chart_viewer_sizes"], restaurado en la siguiente apertura).
Tests:
tests/unit/test_overview_panel.py,test_projects_hub.py,test_style.py(preset panel, override de tamaño, márgenes ajustados) y el nuevotest_chart_viewer.py(ajuste sin scrollbars, ajuste que sigue a la ventana, botones de zoom, memoria de tamaño por gráfico) actualizados al contrato nuevo (6 pestañas, panel dentro de la pestaña «Detalles», slots ocultos sin datos, grupo oculto sin gráficos).pytest tests/unitverde (247 tests, sin red).
Reglas por fase: una fase = un commit; cada fase deja la app funcional con sus tests; no mezclar dos fases sin que la anterior esté verificada (regla de este documento, §7). Tests offscreen con el patrón de tests/unit/test_tonight_table.py (QApplication + theme.apply_theme + panel directo + payloads fake, sin red).
PUNTO DE ENTRADA (para cualquier IA o humano que retome la Fase D):
Leer esta sección 7ter completa, en orden.
Empezar por D1 (
gui/overview.py), validar la base, y proseguir en orden D2 → D3 → D4 → D5 → D6.No tocar
core/ni la máquina de pasos; si surge que toca, detenerse y abrir ADR/decisión nueva antes de escribir el mínimo.Cabecera GPL obligatoria en todo
.pynuevo, código en inglés, cadenas visibles en la GUI porself.tr()y pares ES/EN pororbits.pick(otr()para los estáticos). Idioma activo:config.get("language")(patrónMainWindow._lang,gui/main_window.py:227).La Fase D está independiente de los próximos pasos sugeridos que siguen más abajo (horizonte real, formatos nativos, flujos de cometas/tránsitos).
Antes de la Fase D, el PUNTO DE ENTRADA histórico (proyecto UX v3, fases 1-6):
Leer, en este orden:
docs/adr/ADR-019,ADR-020,ADR-021,ADR-022y este documento completo.Las fases 2 y 3 ya están implementadas en la rama
feature/ux-v3-projects: - Fase 2:core/horizon.py,core/exposure.py, helpers encoords.py, penalización Luna ensuggest.py, integración enplanner.py, silueta enviz/sky_view.py, config ampliada, mocktests/fixtures/horizon_sample.txt+ tests. - Fase 3: migracióncore/db.py(user_version0→1: tablasprojects,project_steps,project_files;observations+=project_id),core/project.py(modelo + máquina de pasos Plan→Captura→Procesado→Análisis→Publicar), tests. - Fase 4: GUI v3 — 4 pestañas (Esta noche · Proyectos · Solar · Historial),projects_tab.uicon lista + stepper, Explore/Post/Blink como diálogos modales contextuales (pre-rellenados desde proyecto), acceso ad-hoc desde menú Herramientas, tarjetas de Esta noche con «Crear proyecto» + ventana + Luna, Settings ampliada con grupos Cámara/Horizonte/Sesión/Luna,sky_viewcon horizonte real, i18n (.ts/.qm) actualizado. - Fase 5:core/sequence.py(exportadores NINA JSON / CCDciel.targets/ CSV genérico para secuencias de captura) ycore/ephemeris.py(generación vía Horizons + exportadores CSV / TheSkyX / Cartes du Ciel para efemérides). Panel «Capture plan» en el hub de Proyectos con campos frames/exposición/filtro y botones de exportación. Los formatos nativos de NINA/CCDciel/TheSkyX/CdC son puntos de partida que requieren validación contra las versiones del usuario en importación real. - Fase 6:core/mpc_report.py(validador de medidas pegadas en MPC 80-col o ADES PSV: formato, código de observatorio, designación; empaquetado del fichero para envío al MPC). Panel «MPC report» en el hub de Proyectos. Subcomando CLInightscribe project list|create|advance|show. i18n completo (208 cadenas ES/EN). Todas las fases completas. El horizonte de seguridad TheSkyX está completo (E1+E2, 2026-08-28): (E1)core/horizon.pyinterpreta la exportación de límites de SkyX (docs/limits-sample.hrz= referencia canónica) además del textoaz alt, con 16 tests de seguridad y rechazo de ficheros rotos. (E2) El horizonte válido manda: en Ajustes desactivamin_alt(tooltip explicativo) yhorizon_margin_degsuma sobre él. El rango seguro es visible en toda la app (ADR-020):planner.safe_window_forcalcula span seguro +best_time+latest_safe_start;sky_viewsomorea el span y marca «empezar hasta HH:MM»; las tarjetas de Esta noche y el hub del proyecto muestran chip verde «⊕ HH:MM–HH:MM · ≤ HH:MM» ochip rojo «⚠ no cabe» cuando la sesión planificada no encaja; la prosa ES/EN incluye la ventana y su aviso («NO forzar el equipo»). 313 tests unitarios en verde. Los formatos nativos de CCDciel ya son reales:export_ccdcielescribe la lista.targets(CONFIG Version="5") fijada contra una exportación real del usuario (docs/ccdciel_sequence_sample.targets, 2026-09-06), con pasos Light + Dark + Bias (calibración configurable en la pestaña Captura del proyecto) y la ventana rise/set por defecto que CCDciel recalcula con su configuración del observatorio. NINA y CSV genérico siguen siendo puntos de partida que requieren validación contra las versiones del usuario en importación real (ADR-021).
Próximos pasos sugeridos (fuera del rediseño inicial):
ApproachChart(plan 2026-09-04): carta vectorial animada geocéntrica (Tierra / Luna de referencia) como 5º slot del grid del panel. El plan completo está endocs/PLANS/approach-chart.md.(Hecho 2026-08-28: el horizonte TheSkyX real ya se interpreta y manda sobre
min_alt— E1+E2 arriba.)Icono lunar real (2026-08-27):
gui/moon_icon.pycompone el disco de
la Luna (assets/moon_disk.png, foto CC BY-SA 3.0 — assets/ATTRIBUTION.txt) con el terminador por geometría exacta (r·cos E), creciente a la derecha / menguante a la izquierda, según ephem_minor.moon["elong_deg"]. Sustituye el % de la cabecera de Tonight (tooltip: % ahora vs. al amanecer + por qué varía en la misma noche) y el emoji del almanac de Solar. Fallback a disco plano gris si falta el asset. 8 tests offscreen, sin red.
Validar los formatos de exportación de secuencias y efemérides contra software real.
Añadir flujos para cometas y tránsitos en el mismo esqueleto de 4 pasos.
Probar la GUI a fondo y refinar la UX del stepper y los diálogos contextuales.
Reglas vigentes: código en inglés con cabecera GPL y comentarios
# @args:, cadenas de GUI porself.tr(), red solo desdecore/sources/víacore/db.py, documentación bilingüe (ADR-013), tests unitarios por módulo + funcionales de flujo.
7quater. Filtro de tipo de objeto sobre la pestaña «Esta noche» (2026-08-30)
Motivación: el filtro de tipo (NEO / SN / CMT / PCCP / TRN / ALT) vivía hoy solo dentro de la tabla colapsada (cascada grp_list / cmb_filter), a la altura del piano: las filas amplias del grid superior y el podio (anillo / color más intenso) siempre se montaban sobre todos los objetivos de la noche, mezclando clases. El observador no puede decirle a la app «hoy solo quiero ver supernovas» ni «deja de mostrar cometas». K1 sube el filtro a la cabecera y lo hace válido para la vista entera (filas + tabla); K2 convierte el tipo mostrado en una preferencia permanente (lista blanca) en Ajustes.
Decisiones pactadas (2026-08-30):
Un solo filtro en la cabecera (K1). El
cmb_filtersale degrp_listy se instala en la fila deTonight(junto albl_contextylbl_moon). Al cambiarlo, ambas vistas se actualizan a la vez: el grid recalcula sus filas con el tipo elegido y su podium sobre lo visible (los 3 primeros del conjunto filtrado, no de la noche completa), y la tabla se repone con las columnas propias del tipo (TABLE_COLS[kind]). Fuente de verdad única:MainWindow._visible_targets()(whitelist + filtro); grid y tabla la invocan.Lista blanca de tipos habilitados en Ajustes (K2). Se añade
config["enabled_kinds"](defecto = 6 tipos) y un grupo «Tonight: object kinds» entab_observingcon 6 checkboxes (NEO, SN, CMT, PCCP, TRN, ALT). Al pulsar Guardar, el combo solo muestra los tipos habilitados + «All»; si el tipo activo acaba de deshabilitarse, cae a «All»; si la whitelist creció, el combo crece también. «All» significa todos los tipos habilitados, no todos los de la noche.core/no se toca: toda la filtración vive engui/main_window.py—config.py(defaults),ui/tonight_tab.ui(header combo),ui/settings_dialog.ui(grupo kinds),main_window.py(_rebuild_kind_filters,_enabled_kinds,_visible_targets,_apply_kind_filter).suggest.top_nsigue diversificando;enabled_kindssolo corta lo que se muestra en la pestaña, nunca el scoring.Persistencia:
config["tonight_kind"]("" = All) se escribe en cada cambio del combo y se restaura al arrancar si sigue en la whitelist. No hay un nuevo ADR: es una decisión de UX local sobre el plano ya documentado en ADR-019/ADR-026 (fuente única deKIND_LABELS/KIND_COLORS).Tests:
tests/unit/test_tonight_kinds.py(offscreen): combo en la cabecera con los 7 items (All + 6), grid y tabla acotados al tipo, podium recalculado sobre lo visible, persistencia detonight_kind, whitelist que recorta/crece el combo, fallback a «All» al deshabilitar el tipo activo.pytest tests/unitverde.
### 7quinquies. Tope «mejores por tipo» + anillo best-of-kind (K3, 2026-08-30)
Motivación: con K1+K2 la «noche» puede traer 15 NEOs del NEOfixer, 90 SNs del Rochester, 15 cometas del COBS… y el grid los pintaba todos a la vez (muro de una sola clase que abocaba a filtrar por tipo para ver algo). K3 introduce el tope mejores de cada tipo (variante configurable, no agrupada) directamente en el grid, y el anillo pasa a caer sobre el mejor de cada tipo visible — no sobre «los 3 primeros del conjunto».
Decisiones pactadas (2026-08-30):
core/suggest.best_per_kind(scored, n): entrada ya puntuada y ordenada por score global (la que devuelvetop_n); mantiene como máximonde cada tipo (defectoconfig["best_per_kind_n"] = 5, configurable 1..50);n<=0= sin tope. Devuelve(grid, best_ids):gridconserva el orden por score global (no agrupado por tipo), ybest_ids= el id del mejor de cada tipo presente (el anillo).Grid «Esta noche» (
_build_suggestion_grid): filtra por «visible» (K1+K2), aplicabest_per_kind, y pinta el resultado. El anillo cae al mejor de cada tipo visible (no a los i<3), y el botón de estado sigue en la derecha. El grid se reconstruye al cambiarbest_per_kind_ndesde Ajustes.Tabla «Esta noche»: sin cambios — la lista completa (los 6 grupos, los scores, los filtros de observación) sigue siendo el conjunto completo; solo cambia su orden y su tinte (ya era best-per-rank 0/1/2), que ahora también es top-3 global sobre lo visible.
suggest.top_n(CLI) no se toca.Planner
_comet_targets(bottleneck): las 15 ephémerides Horizons van en paralelo (pool de ~4 workers, loscobssiguen siendo los 15 más brillantes).n_comets=15se conserva.Config:
config["best_per_kind_n"] = 5(nuevo, default 5) yui/settings_dialog.uitab_observing→ filaspn_best_pk(SpinBox 1..50) con traducción ES/EN.Tests:
tests/unit/test_best_per_kind.py(6 casos: tope, mejor por tipo, sin tope, orden, id vacío, entrada vacía);test_tonight_rows.pyactualizado — ahora toda fila del fixture lleva anillo (un por tipo ⇒ best-of-each);test_tonight_kinds.pyactualizado — el pccp (rank 4 global) SÍ lleva anillo al cambiar al filtro «pccp» (y SÍ lo conserva en «All»).pytest tests/unitverde (345 tests, 13.1 s).Idiomas: 346 cadenas (2 nuevas), 0 unfinished.
Punto de entrada (para cualquier IA o humano que retome esta pantalla):
Leer esta sección 7quater, ADR-019 (hub de pestañas) y ADR-026 (fuente única de temas).
Empezar por K1 (
tonight_tab.ui+main_window.py_apply_kind_filter/_visible_targets), validar el contrato (grid + tabla a la vez, podium sobre lo visible), y proseguir con K2 (config.enabled_kinds+ grupo kinds ensettings_dialog.ui+ guardas enon_open_settings).No tocar
core/para filtrar — si surge que toca, abrir ADR nuevo antes de escribir el mínimo (el planificador ya devuelve los 6 grupos, el scoring sigue diversificando; la whitelist solo corta la vista).Cabecera GPL obligatoria en todo
.pynuevo, código en inglés, cadenas visibles en la GUI porself.tr()(patrón demain_window.py), pares ES/EN pororbits.pick(otr()para los estáticos). Idioma activo:config.get("language")(patrónMainWindow._lang).
### 7ses. Fase E — Punto de entrada unificado (2026-09-02)
Motivación: con las fases D y K hechas, las formas de «ir hacia un objeto» estaban repartidas: clic en la fila → Explorar, doble clic en la tabla → creaba un proyecto en silencio, botón de la fila → proyecto. El observador no podía elegir conscientemente entre explorar y trabajar sobre un proyecto; y la tablea (la vista más rápida) no le enseñaba el contenido del objeto antes de crear nada. Fase E unifica: un solo punto de entrada («Explorar») y los botones Explorar/Continuar son puras atajos; el proyecto se crea o se reanuda desde dentro del diálogo Explorar.
Decisiones pactadas (2026-09-02):
Clic en fila → Explorar (ya era así; se mantiene).
Doble clic en la tabla → Explorar (antes: creaba proyecto en silencio — ahora es
_table_open_explore, mismo destino que la fila y que el botón Explorar).Botón de la fila (
_card_button): verde «▶ Continuar» cuando ya hay un proyecto activo para ese objeto (corto a_start_or_continue, que salta al hub); naranja «🔭 Explorar» si no (abre el diálogo Explorar con ese objeto). El botón es un atajo, no una acción distinta de las de la fila.
Diálogo Explorar (
_open_explore_dialog): la única acción del panel es el CTA único (fondo de la sección de gráficos, ancho completo, 46 px). El «Crear post» de D5 desaparece: los posts se escriben dentro del proyecto (paso Publicar) o a demanda desde Herramientas. La cara del CTA depende delproject_lookupque la ventana le inyecta: verde «Continuar proyecto» si existe un activo para el objeto, naranja «Crear proyecto» si no. Emite una de las dos señales nuevas, cada una con dos argumentos (name, fallback):project_create/project_continue. El diálogo se autoajusta al contenido con suelos de lectura (resize_to_panel_contentengui/overview.py: ancho ≥ 780, alto = sizeHint + ancla de marco) para que los textos se lean cómodos y el CTA quede siempre visible sin scroll vertical. (la anteriorproject_action(name, fallback, continue_)se elimina).MainWindowreacciona en_on_create(va a_create_project) y_on_continue(va a_goto_active_project; si el «nombre explorado» no coincide con el activo, crea uno): en ambos casos el diálogo se cierra después._create_projectahora retorna eldictcreado (oNone,
con un aviso en la barra de estado si el tipo no es válido para proyecto — p. ej. un transit ad-hoc desde el menú Herramientas no puede crear un proyecto, solo un post).
_goto_active_project(name, fallback=None)(nuevo, privado): busca el proyecto activo cuyoobject_namecoincide connameo con elid/namedelfallback(los NEOCP/PCCP guardan el número MPC comoidy un nombre provisional comoname; cualquiera de los dos puede ser el que quedó en el proyecto), y selecciona en el hub. DevuelveTrue/False.
El panel sigue siendo testeable: el
ObjectPanelaceptaproject_lookupcomo callable inyectado (no importacore.dbdirectamente —MainWindow._explore_panello construye);for_post=False(hub) → el CTA se oculta;for_post=True(Explorar) → el CTA se muestra en cuanto el objeto está cargado y la consulta dice qué cara ponerse.
Tests: tests/unit/test_overview_panel.py (7 nuevos: parejas ocultas sin lookup, «Continuar» cuando hay activo, «Crear» cuando no, señales con el continue_ correcto, ignorado mientras carga, _blank las reseta); test_tonight_rows.py (3 nuevos: botón «Explorar» conectado a _open_explore_dialog, botón «Continuar» conectado a _start_or_continue, y la senda fallback «continuar» → «crear»); test_projects_hub.py (4 nuevos: _goto_active_project por nombre, por fallback id, sin coincidencia, y el project_lookup del diálogo honra el id/name del planner); test_tonight_table.py ya cubría el doble clic. pytest tests/unit verde.
Idiomas: 4 cadenas nuevas («Continuar», «Explorar», «Continuar proyecto», «Crear proyecto» + sus tooltips), pares ES/EN en ambos .ts y ambos .qm recompilados.
Punto de entrada (para quien retome esta fase):
Leer esta sección, ADR-019 (hub) y ADR-026 (temas).
Los 4 puntos de contacto con
gui/:_card_button,_table_open_explore,_open_explore_dialog,_explore_panel(enmain_window.py) y el constructor deObjectPanel+_refresh_cta/_cta_clicked(enoverview.py).No tocar
core/project.pypara añadir tipos —VALID_KINDS
sigue siendo la fuente única; si un tipo nuevo entra, añade el caso a los tests de creación (el diálogo ya lo honra por el kind guardado en el target).
Cabecera GPL, código inglés,
self.tr()en la GUI,pyside6-lreleasetras cada cambio de.ts.
Corrección (2026-09-02, mismo día): los tests de la pareja y el smoke de i18n fallaban porque la API de la pareja es ahora el CTA único. El ObjectPanel expone btn_project (un solo botón a ancho completo, min-height: 46, fondo de la sección) y las señales project_create(name, fallback) / project_continue(name, fallback) (2 args cada una; post_requested y project_action se retiran). La ventana conecta los dos en _on_create / _on_continue (main_window.py:1979), el «orquestador» anterior (_do_project / _make_post / _explore_post) queda eliminado. Los 9 tests de test_overview_panel.py (CTA) + los 4 de test_projects_hub.py + los 2 de test_tonight_rows.py pasan.
7sex. Fase F — Gráficas vectoriales en la GUI (2026-09-03)
Motivación: los charts del resumen (órbita, cielo, …) eran PNGs de ratio fijo mostrados en lienzos de otro ratio (letterbox) y no se podían animar, hovernear ni exportar "lo que se ve". Ahora son widgets vectoriales nativos de PySide6 (QGraphicsView). La red / post sigue en matplotlib (ADR-010, intacto para ese ámbito); solo la capa de visualización de la GUI cambia. Ver ADR-029 (diseño) y docs/PLANS/qt-chart-widgets.md (plan fase a fase).
Decisiones pactadas (2026-09-03):
Paquete
gui/widgets/:ChartViewbase (zoom bajo el cursor, pan, fit, export PNG, hover) +OrbitChart(animación por fecha + hoverr/ν/t) +SkyChart(hover hora/altura/azimut, ventana segura, mejor hora).core/orbit_math.pyycore/sky_math.pyguardan la matemática pura sin matplotlib (la compartenviz/*ygui/widgets/*);gui/widgets/palette.pyaporta las constantes de color.chart_viewer.pydual-mode: abre el widget (zoom/fit →widget.view, export →widget.export_png()) o, para los que no tienen widget, el PNG (QLabel+QScrollArea, con botón 1:1). Fábricas:open_chart_widget()/open_chart().Slots del panel: órbita y cielo →
OrbitChart/SkyChartvivos (zoom/pan/hover/animación). transit (curva de luz) y field (cutout SN) →QLabel+QPixmap(aún sin vectorial).build_charts(encore/post.py) sigue como "¿se puede?" y sigue produciendo los PNGs de redes.Panel vs viewer (decision abierta cerrada): el panel retiene su instancia viva; un click reconstruye una segunda instancia y abre el viewer (la opción "dos instancias", la más simple). No se persiste el estado (zoom/fecha) del widget entre sesiones — se re-deriva de
eal abrir.
Estado: las 5 fases (1 base · 2 órbita · 3 cielo · 4 integración · 5 docs) están hechas (commits fa94696, bedf0ce, f276237, 6b66bc6/797173e/ba6d437). Suite verde: unit 389, functional 42 (+1 skip).
Punto de entrada (para quien retome):
Leer ADR-029, ADR-010 (sección "Alcance") y
docs/PLANS/qt-chart-widgets.md.Los points de contacto:
gui/widgets/{base,orbit,sky}_*.py(capa nueva),gui/chart_viewer.py(dual-mode),gui/overview.py(_render_charts,_make_vector,_SlotClick). Los slots transit/field siguen en pixmap: un vectorial de curva de luz (o de cutout) sería el siguiente paso.No importar
matplotlibdentro degui/widgets(regla del plan, probada por test). Las fuentes de datos viven encore/*.py(puras) ycore/post.py(PNGs de redes, intacto).Cabecera GPL, código inglés,
self.tr()en la GUI.
Cada fase deja la app funcional e incluye sus tests. No mezclar fases en un mismo commit sin que la anterior esté verificada.
7septies. Ficha de objeto unificada (2026-09-07, ADR-031)
Motivación: la ficha solo daba tabla de parámetros a NEO/cometa/PCCP; SN y tránsitos se quedaban en hook + bullets, la tabla cortaba las explicaciones largas, las coordenadas no se veían, «Discovered» no aplicaba a NEOs y el filtro de apertura de ADR-015 nunca se había implementado. Plan: docs/PLANS/object-card.md (rama feature/object-card).
Qué cambia (un subplan = un commit):
Coordenadas copiables: bloque bajo el hook con AR/Dec en decimal y sexagesimal + botón «Copiar» (
_coords_from: misma cadena de fuentes que la carta de cielo).Tabla multilínea: wordWrap + auto-alto de fila que sigue a la columna elástica al redimensionar (ojo:
sectionResizedllega antes de quecolumnWidth()se actualice — el handler fuerza el ancho notificado).Tabla para SN:
orbits.explain_transient(tipo, galaxia, distancia, brillo, descubrimiento; z a fondo).Ficha de tránsitos: bug arreglado — la rama exoplanet de
enrichpierde el evento ExoClock; ahora se fusiona con el patrón ADR-027. Tablaorbits.explain_transitcon inicio/fin UTC, profundidad (mmag y %), duración y veredicto telescopio mínimo vs. tu apertura.Chips por tipo con la misma gramática: SN gana tipo y frescura («N d», verde si ≤14 días); tránsito gana profundidad Δmmag.
«Discovered» exacto en NEOs y PCCP: SBDB
discovery=1(fallbackfirst_obs), NEOfixer/orbit/para NEOCP, PCCP mapea su propia columna. Pool paralelo de 4 + caché 7 días. Nuevocore/dates.pyúnico.Filtro duro de apertura en tránsitos:
transits_tonight(aperture_in=)+ interruptortransit_scope_filteren Configuración > Observación (activado por defecto; sin dato ExoClock no se descarta — ADR-025).
Estado: subplanes 0-6 hechos; suite unitaria verde (555). Commits: 72d66ee (plan), b163f6c, 376a23c+67ceadf, 02c05bf, 3412c3c, 8c4c336, afab280, 03290ea + cierre i18n/docs.
Nota i18n: el comando lupdate documentado en CONTRIBUTING ahora incluye gui/widgets/*.py — sin él, lupdate marca «vanished» cadenas vivas de las cartas vectoriales y los tests de i18n fallan al regenerar.
7octies. Goto con efeméride fresca para cuerpos en movimiento (2026-09-07, ADR-030 rev.)
Motivación: los NEOs, cometas y PCCP no tienen coordenadas fijas (a diferencia de SN y tránsitos). El goto usaba el snapshot que el planner guardó al crear el proyecto — un NEO a 5″/min con un plan de hace 2 h acumula 10′ de error, y el goto astrométrico resolvía el campo equivocado. La ficha además mostraba la fila Horizons de las 00:00 UT (eph[0], paso diario), hasta 24 h stale. Plan: docs/PLANS/goto-fresh-ephemeris.md.
Qué cambia (un subplan = un commit):
core/ephemeris.py::position_at: consulta Horizons a paso 2 min en ventana ±2 h (redondeada a 30 min para reusar la caché), interpola linealmente a «ahora» (desenvolviendo AR en el salto 0h/24h), deriva tasa aparente (″/min) y PA. Fallback sin red: SBDB+Kepler → NEOfixer preliminar →None.Goto fresco en
main_window.py: paraneo/comet/pccpla acción delCcdcielWorkerresuelveposition_atantes del slew/solve y pliega el resultado en el contexto (coords_epoch/coords_source/rate). SN y tránsitos siguen usando el snapshot. Etiqueta «Posición a las HH:MM:SS UT» + aviso si cae al snapshot.Ficha con época visible:
_enrich_small_bodypide paso 30 m y elige la fila más cercana a ahora (ephem_epoch); el bloque de coords muestra la época junto al RA/Dec copiable.Cierre: i18n ES/EN (~4 cadenas), revisión de ADR-030, esta sección,
pytest tests/unitverde (582).
Estado: subplanes 0-3 hechos; suite unitaria verde (582). Fuera de esta iteración: SolarTracking/UpdateCoord=True en el .targets y tasas no siderales vía JSON-RPC (necesitan validación contra el CCDciel real); el cap anti-traza por exposición (max_exposure_no_trail + rate_arcsec_min) ya protege los frames.
7nonies. Track A — Ciclo de vida y clasificación de proyectos (2026-09-09)
Plan: docs/PLANS/project-lifecycle.md (hijo A de docs/PLANS/project-concept-v2.md). Rama feature/project-lifecycle.
Los proyectos se pueden cerrar (con resultado final) y reabrir (la revisita «un año después» es un flujo real); el hub clasifica por año, tipo, etiquetas, favoritos, búsqueda y orden; y los ficheros que el proyecto genera se ven en la pestaña Detalles.
| Sub | Entregable | Estado |
|---|---|---|
| A0 | Migración user_version 4: closed_at, outcome, tags, favorite + índice created | Hecho |
| A1 | core/project.py: close()/reopen()/set_tags/set_favorite + OUTCOMES por tipo | Hecho |
| A2 | GUI: botones cerrar/reabrir con diálogo de resultado + asesor de cierre + confirmación de archivar | Hecho |
| A3 | Hub: agrupación por año, filtro por tipo, búsqueda, favoritos primero, orden configurable | Hecho |
| A4 | project_files visibles en Detalles + carpeta por proyecto + registro de blink/FITS | Hecho |
| A5 | CLI project close/reopen/files + i18n ES/EN (458 cadenas) + esta sección | Hecho |
Estado: suite unitaria verde (643).
7decies. Track B — Supernova: seguimiento fotométrico multi-noche (2026-09-09)
Plan: docs/PLANS/sn-followup.md (hijo B de docs/PLANS/project-concept-v2.md). Rama feature/sn-followup.
El proyecto SN deja de ser «una noche» y se convierte en un seguimiento de semanas o meses (e incluso años, con reabrirura): cada visita se registran el apilado final por filtro y la fotometría (sin fricción: solo teclear la magnitud), NightScribe calcula un análisis de campaña indicativo propio y dibuja la curva de luz frente a las plantillas típicas del tipo, genera la animación de la evolución (GIF/MP4), recomienda la captura por brillo, produze el FITS anotado, recuerda la cadencia («hace N noches que no la visitas») y mantiene el post vivo. Referencia real: la página de AT2020sum/AT2020sun en irydeo.com (fotometría AIJ, comparaciones Gaia DR2, tablas por filtro Clear/NIR, revisitas).
| Sub | Entregable | Estado |
|---|---|---|
| B0 | Migración user_version 5: project_sessions, session_images, photometry_points + core/followup.py CRUD | Hecho |
| B1 | core/fits_meta.py: DATE-OBS/FILTER/EXPTIME del header + MJD | Hecho |
| B2 | Pestaña «Seguimiento» en proyecto SN (visitas, apilados, notas, cadencia) | Hecho |
| B3 | Entrada fotometría sin fricción: rápida (solo magnitud) + pegado tolerante (AIJ/Tycho/CSV) + fichero | Hecho |
| B4 | Curva de luz: PNG + widget QGraphicsView + plantillas típicas esquemáticas (Ia, II-P/L, Ib/c, SLSN, kilonova) | Hecho |
| B5 | core/series.py: motor de series — apertura, detección, ensemble automático, Δmag, análisis de campaña con veredicto vs plantilla | Hecho |
| B6 | Animación de evolución: N frames alineados por afín desde WCS, GIF/MP4 | Hecho |
| B7 | Didáctica SN: tipos ampliados + TNS enrich cuando SIMBAD no conoce la SN | Hecho |
| B8 | Captura SN: recommended_sn_exposure(mag) + secuencias multi-filtro retrocompatibles + bloque SN en Plan tab | Hecho |
| B9 | Post vivo: curva de luz en build_charts + etiquetas de animación | Hecho |
| B10 | FITS anotado: copia con keywords NS_ en cabecera (Python puro, sin astropy) | Hecho |
| B11 | Cadencia con memoria: «hace N noches» en pestaña Seguimiento + chip en Tonight | Hecho |
| B12 | Contexto de surveys ASASSN/ZTF (opcional) | Pendente (opcional) |
| B13 | Cierre: i18n ES/EN (467 cadenas), ADRs, esta sección | Hecho |
Estado: suite unitaria verde (749). Fuera de esta iteración: fotometría absoluta propia (términos de color), sustración de galaxia, ajuste PSF; monitor de flujo en vivo para tránsitos/variables de corto periodo (sobre core/series.py, tremadamente simple); importar salida de EXOTIC (O-C); lanzar EXOTIC/AIJ como subproceso.
(Restaurada 2026-09-10: esta sección se perdió al resolver el merge 50cd6c9 — se recupera verbatim de d0686e4.)
7undecies. Track C — NEO/cometa: consistencia con el resto de flujos (2026-09-10)
Plan: docs/PLANS/neo-consistency.md (hijo C de docs/PLANS/project-concept-v2.md). Rama feature/neo-consistency.
El flujo NEO/cometa ya era suficiente funcionalmente; este track lo hace consistente con lo que ganaron las supernovas en el track B: el paso Process registra lo que el observador realmente conserva de la sesión (los FITS con metadatos auto-leídos, las imágenes anotadas de Tycho y el reporte MPC — este último ya se registraba) y ofrece la animación de movimiento como prueba de fuego: frames alineados a las estrellas (afín por WCS), recorte que sigue la posición predicha en el DATE-OBS de cada frame (ephemeris.position_at), marcador sobre la predicción y caption con fecha/hora, tasa/PA previstos y fuente de efeméride (obligatoria: la prueba solo vale lo que vale la efeméride). Si un punto permanece bajo el marcador mientras las estrellas derivan, es ese objeto.
| Sub | Entregable | Estado |
|---|---|---|
| C0 | Análisis documentado + bloque «Session products» en Process (neo/pccp/comet): registro multi-FITS (metadatos vía fits_meta), imágenes anotadas (kind='image'), persistencia en project_steps.data + project_files | Hecho |
| C1 | viz/motion_view.py: animación GIF/MP4 siguiendo la posición predicha (alineación estelar por afín WCS, caption con tasa/PA/fuente, salto de frames sin WCS/fecha/efeméride) + botón en Process con zoom configurable | Hecho |
| C2 | Sin cambios (C0 no levantó huecos); cierre: i18n ES/EN (545 cadenas, 59 nuevas — incluidas las pendientes del cableado B5/B6/B10), cadenas fuente del widget de curva de luz corregidas a inglés, esta sección | Hecho |
Extra no previsto: el test «afín correcto» de C1 destapó un bug latente de B6 — _compute_affine devolvía la transformación frame→referencia, pero PIL Image.transform(AFFINE) muestrea output→input: la deformación era la inversa (invisible con WCS casi idénticos, incorrecta con campos desplazados/rotados). Corregido muestreando en espacio de referencia y verificado con una estrella a posición de cielo fija bajo traslación y rotación. La animación de evolución SN (B6) sale igualmente beneficiada.
Estado: suite unitaria verde (824). Fuera de alcance (del plan): curva de luz de asteroides (v2 si el motor de series madura), rediseño del flujo de medida MPC (ya validado, ADR-022), secuencias multi-filtro en el plan NEO (la capacidad de B8 queda disponible).
7duodecies. Track D — Exoplanetas: captura guiada y handoff EXOTIC (2026-09-10)
Plan: docs/PLANS/exoplanet-transit-project.md (hijo D de docs/PLANS/project-concept-v2.md). Rama feature/exoplanet-transit-project.
El proyecto de tránsito dice al observador cuándo empezar a capturar, cuándo ocurre el tránsito y cómo debe ser la secuencia, pensado para quien nunca ha capturado uno («que cualquiera se atreva»). La reducción es 100 % externa con EXOTIC (NASA/JPL): NightScribe exporta el inits.json pre-rellenado y guía el cierre del flujo científico (subida a ExoClock / AAVSO Exoplanet Database) además del post. La selección lidera con la decisión real: «cabe entero en tu noche (con baselines)» + «detectable con tu telescopio», y la prioridad ExoClock se explica didácticamente.
| Sub | Entregable | Estado |
|---|---|---|
| 0 | core/transits.py: recommended_window (baseline = max(25 % dur, 30 min) Conti/CN), capture_start/end, baseline_fits (oscuridad + horizonte en ambos bordes), cadence_max_s (≥3 puntos en el ingress), exp_recommended_s; gate de horizonte intacto en mid-transit | Hecho |
| 1 | suggest: frase «empieza a capturar a las HH:MM UTC», aviso ⚠ + penalización −4 cuando la baseline no cabe, veredicto de apertura y prioridad ExoClock didáctica | Hecho |
| 2 | Bloque transit en Plan & Capture: línea de tiempo visual (TransitTimeline, widget QGraphicsView ADR-029: noche / sobre el horizonte / captura con baselines y milestones), tira de tiempos UTC+local, exposición heurística preseleccionada, cadencia con overhead explícito + aviso «no resuelves el ingress», línea altura/Luna y checklist pre-vuelo de 5 pasos persistente en project_steps.data | Hecho |
| 3 | Export con ventana: CCDciel StartTime/EndTime + MandatoryStartTime=True (StartRise off — best-effort hasta validar contra el CCDciel real), NINA metadata, CSV con start_time por frame | Hecho |
| 4 | Handoff EXOTIC: TAP ampliado (pl_orbincl/pl_orbeccen/st_logg/st_metfe/pl_orbsmax/pl_tranmid/sy_pmra/sy_pmdec, bump de caché v2), core/exotic.py (esquema re-verificado contra el inits.json vivo de rzellem/EXOTIC: claves exactas, fecha «17-December-2017», fórmula Rp/Rs corregida con st_rad), Settings (código AAVSO, tipo de cámara, binning — la elevación reutiliza height), botón en Process + guía de subida a ExoClock/AAVSO | Hecho |
| 5 | Cierre: i18n ES/EN (582 cadenas, 37 nuevas), revisión de ADR-015 (ventana + cadencia + heurística + EXOTIC) y ADR-019 (bloque transit en Plan, EXOTIC en Process), esta sección | Hecho |
Estado: suite unitaria verde (868). Fuera de esta iteración (del plan): leer la salida de EXOTIC (Mid-Transit Time → O-C, marca observed) — v2; monitor de flujo en vivo sobre core/series.py (horizonte: variables de corto periodo, condición tremendamente simple); lanzar EXOTIC como subproceso; validación del MandatoryStartTime y del inits.json contra el software real del observatorio (pendiente de una ejecución del usuario).
7terdecies. Carpeta contenedora de proyectos (2026-09-10, ADR-032)
Sin docs/PLANS/ propio: salto corto decidido en ADR-032. Todo lo que genera un proyecto (plan, secuencias, FITS, post, gráficos) caía en data_dir()/projects/<id>-<slug> (fijo de platformdirs): el usuario no sabía dónde acababan sus ficheros ni podía llevárselos a su disco de trabajo. Se da control sobre la carpeta contenedora: raíz global configurable y, si hace falta, carpeta distinta por proyecto.
| Sub | Entregable | Estado |
|---|---|---|
| P1 | Migración user_version 6: root_dir en projects + backfill a la ruta heredada; config.projects_root; paths.project_dir(root=) sigue puro; project.storage_dir() (root del proyecto → config → defecto), set_root_dir() y create congela la raíz; tests test_project_storage.py | Hecho |
| P2 | 9 call sites de main_window.py escribiendo vía project.storage_dir() + CLI project show imprime folder: | Hecho |
| P3 | Ajustes > Observación: grupo «Projects folder» (Browse/Reset de projects_root) + botón «Change folder…» en el hub (re-ubica solo exportaciones futuras, v1 no mueve ficheros); tests test_settings_storage.py + ampliados | Hecho |
| P4 | Cierre: i18n ES/EN (598 cadenas, 11 nuevas), lrelease, ADR-032, esta sección | Hecho |
Estado: suite unitaria verde (895). Comportamiento: el cambio de raíz global solo afecta a proyectos nuevos; los heredados conservan su carpeta exacta congelada en root_dir; el botón del hub re-ubica únicamente lo que se escriba a partir de entonces.
7quaterdecies. Track HADS — estrellas variables de alta amplitud (2026-09-11, ADR-034)
Plan: docs/PLANS/hads-stars.md (maestro) + docs/PLANS/hads/fase-*.md (20 subplanes autocontenidos). Rama feature/hads.
Nuevo tipo de objetivo hads: δ Scuti de gran amplitud (periodos 1–5 h, ΔV ≥ 0.3 mag) — se ven pulsar en directo. Sin fase conocida → la recomendación es una captura continua de 2×P (no un evento); el gate de listado exige una ventana contigua ≥ 1 ciclo. La fuente es híbrida: el libro de Google Sheets de Patrick Wils (se actualiza a diario) se descarga en runtime con caché de 12 h (core/sources/hads_sheet.py, XLSX parseado solo con la stdlib, doble nivel: crudo + JSON) y se fusiona con el snapshot empaquetado (assets/HADS-stars.csv, respaldo offline + aliases). La leyenda de colores del libro alimenta el score: rojo/naranja (¡cambios de periodo!) +12/+8 de urgencia, azul (no observada) +6, cobertura del mes en hueco +10 — nunca apiladas (manda la mayor).
| Sub | Entregable | Estado |
|---|---|---|
| H0.1–H0.5 | Lector XLSX stdlib, caché TTL 12 h, parseo de colores + cobertura, core/hads.py (catálogo/merge/derivados/diente de sierra), docs de datos | Hecho |
| A.1–A.3 | Fase hads en el planner (gate 1 ciclo, sesión 2P en _visibility), scoring (4 familias), fragmentos ES/EN | Hecho |
| B.1–B.4 | GUI listable (color magenta, icono, columnas, fase), config + migración amable + combos, enrich (antes de la regex exoplaneta), ficha con explain_hads + chips | Hecho |
| C.1–C.3 | Proyectos hads (reported_aavso), narrativa ES/EN citando a Wils/VVS/VSX, post/tuit | Hecho |
| D.1–D.5 | Bloque de plan (2P, cadencia ≤ P/12, checklist), secuencia CCDciel (ventana advisory), Follow-up + process (FotoDif/WebObs), plegado por fase (widget + PNG) | Hecho |
Estado: suite unitaria verde (989) + funcional con red verde (test_hads_live). Fuera de esta iteración: monitor nativo en vivo (aparcado — FotoDif AUTO ya cubre el directo); la generalización a variables de largo periodo y campañas (modelo pactado: campaña = atributo ortogonal del proyecto + kind variable) será el siguiente track, en su propia rama (ver «Generalización futura» en el plan maestro).
7quindecies. Track V — Variables de largo periodo y campañas (2026-09-11, ADR-035)
Plan: docs/PLANS/variables-campaigns.md (maestro) + docs/PLANS/variables/fase-*.md (40 subplanes). Rama feature/variables-campaigns.
Nuevo kind variable (molde multi-noche del Track B; las HADS eran la excepción) y entidad campaña 1:N ortogonal (migración v7), tomada de las campañas reales del grupo obsSN (WeSb 1, T CrB). Tonight gana la fase local campaigns (solo vencidas, V-d); ficha con VSX (subdominio vsx.aavso.org, el www está tras Cloudflare) → SIMBAD → manual; predicción de extremos con la época VSX; asesor de eventos (dip/erupción, umbral configurable); contexto de surveys ZTF/ALeRCE en la curva (cierra la opción B12); reporte CSV + AAVSO EFF con HJD calculado en la app (Sol de Schlyter).
| Sub | Entregable | Estado |
|---|---|---|
| V0.1–V0.9 | core/variables.py, core/campaign.py, core/sources/vsx.py (TTL 7 d), core/sources/surveys.py (ALeRCE ZTF, TTL 30 d), migración v7 | Hecho |
| VA.1–VA.4 | Fase planner campaigns (vencidas + visibilidad), sub-dict variable, scoring de campaña, fragmentos ES/EN | Hecho |
| VB.1–VB.6 | GUI listable variable, ficha VSX→SIMBAD→manual, plegado por época, contextos | Hecho |
| VC.1–VC.10 | Proyectos variable, gestor de campañas (crear/editar/finalizar, objetivos, adjuntar), hub por campaña, Follow-up | Hecho |
| VD.1–VD.9 | Chip Tonight generalizado + aviso de evento, plan, secuencia multi-filtro, photometry_export.py (CSV + AAVSO EFF), diálogo export, botón surveys, narrativa, post con curva plegada | Hecho |
| VE.1–VE.2 | i18n completo (714 cadenas, 0 unfinished), cierre documental (este track) | Hecho |
Estado: suite unitaria verde (1096). Fuera de esta iteración: ventana horaria de eclipses, compartir campañas, AAVSO Alert Notices, ASAS-SN Sky Patrol, monitor en vivo.
Siguiente track: la auditoría de usabilidad (2026-09-13) encontró las funciones del Track V correctas pero escondidas (el gestor de campañas es un selector sin vista de detalle) y el flujo de proyecto con dos modelos de navegación apilados (pestañas + wizard). La corrección es su propio track — plan escrito, pendiente de ejecutar en la rama independiente feature/campaigns-ux: docs/PLANS/ux-variables-campaigns.md (maestro) + docs/PLANS/ux/fase-*.md (23 subplanes: la pestaña Campaigns, enlaces bidireccionales, un solo lenguaje de gestos, el proyecto como página única con tarjeta «Siguiente acción», y CCDciel en su propia pestaña Observatory). También pendiente, aprobado el 2026-09-12: el add-on de descubrimiento de variables (extremos predecibles + vigilias T CrB/R CrB).
7sexdecies. Track UX — usabilidad de variables y campañas (2026-09-13, enmienda ADR-035 + ADR-019)
Plan: docs/PLANS/ux-variables-campaigns.md (maestro) + docs/PLANS/ux/fase-*.md (23 subplanes). Rama feature/campaigns-ux (independiente: sin merge de vuelta en la cadena de features).
La auditoría de usabilidad (2026-09-13) encontró las funciones del Track V correctas pero escondidas: el gestor de campañas era un selector sin vista de detalle, ninguna mención a una campaña era un enlace, el lenguaje de gestos difería por lista y el flujo de proyecto apilaba dos modelos de navegación (pestañas + wizard). El track entrega: la pestaña Campaigns quinta (maestro-detalle: la salud de cada campaña de un golpe de vista —miembros × cadencia × eventos vía la nueva query campaign.status_report—, protocolo, URLs clicables, tabla de miembros; el gestor modal se jubila y la decisión 9 de ADR-035 queda superseded); navegación bidireccional (badge en la cabecera del proyecto, chip ⚑ en la tarjeta de Tonight, chips de cadencia por proyecto que caen en el Follow-up al hacer clic, doble clic en un miembro → hub, doble clic en History → proyecto/Explore); un solo lenguaje de gestos en todas partes (clic selecciona · doble clic/Enter abre · menú con botón derecho · cursor de mano); el proyecto como página única (tarjeta «Siguiente acción» alimentada por project.next_action() + secciones plegables con estado en palabras llanas —las pestañas de paso y el wizard se jubilan; ADR-019 enmendado, modelo de ciclo de vida sin cambios); la pestaña Observatory sexta (el control de CCDciel sale del paso Plan); y la tanda de correcciones (sin validaciones silenciosas, red fuera del hilo de GUI vía ResolveWorker/SurveyWorker, event_mag_threshold en Ajustes, filtro de campaña persistido, detalle obsoleto limpiado, el CTA ya no cierra al fallar).
| Sub | Entregable | Estado |
|---|---|---|
| U0.1–U0.6 | tanda de correcciones y feedback | Hecho |
| UA.1–UA.7 | pestaña Campaigns + enlaces | Hecho |
| UB.1–UB.3 | lenguaje de gestos | Hecho |
| UD.1–UD.5 | proyecto como página única + pestaña Observatory | Hecho |
| UC.1–UC.2 | i18n + cierre documental (este track) | Hecho |
Emenda (2026-09-19, ADR-041): la «página única plegable» del sub UD (acordeón de 5 secciones) pasó a ser una barra de 5 pestañas planas y perezosas sobre la página — Object card · Plan · Proceso · Publicar · Follow-up —; el resto de este track no cambia.
Estado: suite unitaria verde (1394). Fuera de esta iteración: exportación fotométrica a nivel de campaña, compartir campañas, el add-on de descubrimiento de variables destacadas (aprobado el 2026-09-12, su propio track).
Próximos tracks decididos (2026-09-16 — ADR-036 + ADR-037)
Con el Track UX cerrado, la revisión con el observador fijó el reasentamiento final de la barra de pestañas y los dos tracks que siguen, en este orden (decisiones documentadas; todo en la rama feature/campaigns-ux, decisión del usuario):
Señales y campañas —
docs/adr/ADR-037-campaign-signals.md+ plandocs/PLANS/signals-campaigns.md. Ejecutado completo el 2026-09-16 (SC1–SC5): ver la sección 7septdecies más abajo.Diario & Divulgación —
docs/adr/ADR-036-journal-and-sunsky.md+ plandocs/PLANS/journal-outreach.md. Ejecutado completo el 2026-09-16 (J0–J3, S1–S3): ver la sección 7octdecies más abajo.
7septdecies. Track SC — Señales y campañas (2026-09-16, ADR-037)
Plan: docs/PLANS/signals-campaigns.md. Rama feature/campaigns-ux (todo el trabajo de esta era vive en esta rama, decisión del usuario).
La revisión con el observador detectó dos carencias: la pestaña Campañas era funcionalmente «el hub filtrado por campaña» y los eventos de variables apenas se veían (un evento en un proyecto al día nunca salía en Tonight — due_campaigns filtraba antes de evaluar). El track entrega: la triple condición de listado (vencido o evento detectado o extremo inminente campaign_extremum_days, con campaign.project_signal() como fuente única de verdad — SC1); la consola de señales en la pestaña Campañas (signals_report: eventos ⚡ primero, extremos ⏳ por llegada, cobertura «N de M al día» — SC2); el chip de extremo en las filas de Tonight (convención VSX de época — SC3); y los eventos relevantes de variables en dos canales (SC4, re-scope pactado el mismo día): las vigilias automáticas (core/vigils.py, lista curada T CrB/R CrB editable en Ajustes, última magnitud ZTF/ALeRCE contra basal con caché propia de 12 h — nunca la de contexto de 30 d) y el canal editorial AAVSO (core/sources/aavso.py: alertas del foro —Discourse sirve JSON nativo— y campañas activas parseadas de la app; spike validado antes de escribir). Todo con la regla de fusión (SC-g): una señal sobre una estrella que ya es proyecto se suma a sus razones de listado, jamás una fila duplicada.
| Sub | Entregable | Estado |
|---|---|---|
| SC1–SC3 | triple condición + consola de señales + chip de extremo | Hecho (fcdf443, f96b2e6, 50bdf58) |
| SC4a | vigilias: core/vigils.py, fase planner, fusión, chip 👁, consola solo-caché, editor en Ajustes | Hecho (24 tests test_vigils.py) |
| SC4b | canal AAVSO: spike (Discourse JSON + tabla de campañas viables), core/sources/aavso.py, fase planner, fusión, chip 📣, checkbox en Ajustes | Hecho (17 tests test_aavso.py con títulos reales del spike) |
| SC5 | cierre: i18n ES/EN (809 cadenas, 0 unfinished), revisión ADR-035 (rol de la pestaña), esta sección | Hecho |
Estado: suite unitaria verde (1244). Fuera de esta iteración: ASAS-SN Sky Patrol; el cuerpo de los temas del foro (el título basta para la fila de Tonight); cadencia ZTF recordada en la ficha (la vigilia es red de seguridad, no red de alertas).
7octdecies. Track JO — Diario de observación y «Sol y cielo» (2026-09-16, ADR-036)
Plan: docs/PLANS/journal-outreach.md. Rama feature/campaigns-ux (todo el trabajo de esta era vive en esta rama, decisión del usuario).
La pestaña History estaba muerta por construcción (observations no tenía escritor desde UX v3.1) y «Sistema solar» era un escaparate sin flujo. El track entrega: el Diario de observación — el Historial sale de la barra de pestañas (cinco pestañas: Esta noche · Proyectos · Campañas · Sol y cielo · Observatorio, Ctrl+1..5) al menú Herramientas, reconstruido como vista derivada auto-generada (core/journal.py: UNION de proyectos/visitas/ficheros/fotometría/campañas + marcas legacy, agrupada por noche astronómica mediodía→mediodía local, con filtro por tipo, búsqueda y salto a proyecto/Explorar); el re-cableado del feedback de observación a la actividad de proyectos (project.activity_for: la novedad del score y la columna ✔ —ahora «Covered»— leen el mundo-proyecto, no la tabla huérfana; las filas de compromiso —campaña/vigilia/AAVSO— están exentas del decay); y la pestaña «Sol y cielo» como hogar de la divulgación: línea de «impacto en tu noche» (Luna → débiles, Kp → auroras) con salto a Esta noche, CTA «Generar PNG para redes» (sun_panel.draw_sun con la marca de agua del observatorio, vista en el ChartViewer) y el borrador bilingüe «post del cielo» (narrative.sky_draft + diálogo copiable).
| Sub | Entregable | Estado |
|---|---|---|
| J0–J2 | Diario en el menú + vista derivada + diálogo/CLI | Hecho |
| J3 | re-cableado score/✔ a actividad de proyectos | Hecho |
| S1–S3 | «Sol y cielo» + impacto + PNG redes + post del cielo | Hecho |
Estado: suite unitaria verde (1272), i18n 825 cadenas 0 unfinished. Idea futura anotada: la «crónica de la noche» generada desde el Diario (borrador de post con lo hecho en la sesión). Pendiente de validación con el token del usuario: el camino AAVSO de las vigilias brillantes (ver 7septdecies).
7noovies. Track VU — Informe de surveys y curva inline (2026-09-17, ampliación ADR-035)
Rama feature/campaigns-ux (continuidad natural de la opción B12 que cierra el ADR-035 §7).
El botón de surveys de Follow-up era silencioso por diseño: clic → nada visible (ni puntos, ni confirmación), y la descarga además almacenaba fechas erróneas (un pegado con enteros YMD había pasado 4 filas de un proyecto real con MJDs 4 años atrás — corregido y reparado en la BD). El track entrega:
Informe, nunca silencio (
surveys.fetch_points_detailed): el worker devuelve{status: ok|empty|error, points, error}y la GUI reporta siempre con voz humana: ok → «%1 new, %2 updated, %3 unchanged, %4 removed; MJD min → max; bands (ZTF via ALeRCE)»; empty → «No survey data for this position»; error → «Survey download failed — <razón>».Recliclar = re-consulta real:
force=Truesiempre; la caché de 30 d no oculta el clic.fetch_points(el camino de las vigilias, ADR-037 SC4a) mantiene el silencio[]: la vigilia nunca debe tumbar la app.Upsert idempotente y protegido (
followup.upsert_survey_points): clave(mjd, filter); solo toca filassource LIKE 'survey:%'(jamás paste/manual/file/quicklook) y solo elimina lassurvey:%huérfanas si el resultado es no vacío: una respuesta vacía nunca borra fotometría almacenada. Solo proyectossn/variable; los demás reciben un «Cannot store survey points: <razón>» explícito.Curva inline en Follow-up (
gui/widgets/lightcurve_widget.py): etiquetas humanas por origen («Manual entry», «Pasted data», «From file», «Quick-look · indicative», «Survey · ALeRCE/ZTF»); proyectosnconsn_type: plantilla de SN plegada al pico (toggle ON por defecto);variable: plegado periódica + diente de sierra; líneas de conexión ON por defecto (manual = continuada, quick-look/survey = discontinua — la misma convención semántica que el PNG, solo cambia el texto de la etiqueta).Payload en un sitio solo (
core/lightcurve_data.py::build_payload): el widget inline,post.py(PNG, víaviz/lightcurve_view.py) yoverview.pycomparten la construcción (puntos, prioridad desn_type, pico, plegados, diente de sierra); el crashTypeErrorcuandomineraNone(rama del overview) queda corregido en el sitio común (guarda de mínimos).Pegado con feedback: vista previa con fecha humana y aviso «⚠ date outside 1966–2086 — check» para MJDs fuera de banda (solo vista previa; la ruta de guardado no cambia) y el parser
core/photometry_import.pyya no confunde enteros YMD con MJD (20260909 → 61292en vez de58107), acepta JD (restando 2 400 000,5) y MJD directo, y devuelveNonesi no sabe (sin excepción).
| Sub | Entregable | Estado |
|---|---|---|
| VU.1 | parser pegado YMD/MJD/JD + reparación de filas pid 18 + tests | Hecho |
| VU.2 | fetch_points_detailed + worker + informe ok/empty/error | Hecho |
| VU.3 | upsert_survey_points (idempotente, protegido, con puerta de kind) + tests | Hecho |
| VU.4 | curva inline (etiquetas, toggle de plantilla, líneas de conexión) + build_payload compartido | Hecho |
| VU.5 | aviso de pegado + ampliación ADR-035 + esta sección | Hecho |
Estado: suite unitaria verde (1293): 17 tests nuevos (test_lightcurve_data.py 10, test_surveys.py +4, test_lightcurve_widget.py +3) y 3 tests actualizados al payload dict (test_workers.py ×2, test_projects_hub.py). Fuera de esta iteración: descarga automática al crear el proyecto (el botón manual sigue siendo la vía), mezcla de survey en la vista de AAVSO (los puntos survey:% siguen siendo referencia, nunca suben a AAVSO).
7vigies. Track UX-PC — Projects y Campaigns que dicen «Ohh» (2026-09-17, ADR-038)
Plan: docs/PLANS/ux-proyectos-campanas.md. Rama propia e independiente feature/ux-projects-campaigns (nace de feature/campaigns-ux cb457e6; el merge se decide al cerrar el track).
La auditoría de usabilidad del 2026-09-17 (el observador: «funcionalmente nada que reprochar, pero la UX está cargada; sin manual no es intuitiva — quiero que al abrir Proyectos o Campañas diga "Ohh, eso es lo que necesito"») entrega: el dashboard «Necesita tu atención» como panel derecho de Projects sin selección (3-5 tarjetas con la razón en palabras llanas y un botón que aterriza en la sección exacta; estados calma/vacío; fuente: el nuevo core/attention.py, math 100 % local y aditiva); filas ricas en ambas pestañas (banda de tipo, siguiente acción en palabras, puntos de progreso, chip «⊕ HH:MM–HH:MM esta noche» vía planner.safe_window_for, edad de actividad, sparkline de tus medidas en SN/variables, orden «Te necesita»; desde 2026-10-01 la miniatura es la última curva disponible del proyecto (la corrida más reciente, sea la serie o una reducción de EXOTIC), encuadrada en la misma ventana de magnitudes del gráfico); la tarjeta Next como centro de mando (Mark done/Skip junto a Go →; las secciones conservan solo un pie discreto); la prominencia a tres niveles (primario visible / menú ⋯ / bloque colapsable con título en lenguaje llano — Calibration y «Lo que guardaste de la sesión» arrancan cerrados; las herramientas de fotometría tras un ⋯), afinada el 2026-10-06: en Fotometría y Astrometría el primario es un botón grande pintado en el color del tipo de objeto, con una línea que dice lo que hará, y todos los ajustes en bloques cerrados (ADR-038 rev), y todo, incluidos los resultados, en tarjetas con borde (un grupo sin borde no deja ver dónde acaba); el ciclo de vida consolidado en el ⋯ de la cabecera del proyecto; el control CCDciel completando su mudanza a Observatory (grupo «Live capture» — Send plan lee el plan guardado del objetivo); Campaigns como sala de guerra («Está pasando ahora» — renombrado desde «Signals»— con frases completas ⚡/⏳/👁, tarjetas de salud ●●●○, acciones en la cabecera del detalle con enablement real, «Finish» → «Close»); y la regla de lenguaje llano con ayudas ⓘ (qué es una campaña/proyecto, tooltips en las cinco pestañas, estados vacíos que enseñan). Ninguna funcionalidad se pierde: se reubica.
| Sub | Entregable | Estado |
|---|---|---|
| U1 | cimiento Projects: Filters ▸ + ⋯ gestión; fuera Refresh/Campaigns…/pie | Hecho (35f5a34) |
| U2 | core/attention.py + dashboard + filas ricas + sparkline | Hecho (6145880) |
| U3 | Next card centro de mando + bloques colapsados + CCDciel → Observatory | Hecho (1a08e2e) |
| U4 | Follow-up despejado (⋯ herramientas + bloque colapsado) | Hecho (0b5f5b3) |
| U5 | Campaigns sala de guerra + ayudas ⓘ + «Close» | Hecho (a0e09f7) |
| U6 | lenguaje llano + i18n ES/EN (876 cadenas) + ADR-038 + esta sección | Hecho |
| U7 | la franja «Está pasando ahora» se explica sola: línea de ámbito + ayuda ⓘ de iconos, «Al día: N de M proyectos de campaña» (+tooltip), guía sin campañas, estado vacío cálido ✨ | Hecho (2026-09-17) |
Estado: suite unitaria verde (1299), i18n 876 cadenas 0 unfinished. Métricas: Projects con un SN abierto pasa de ~20 a ≤10 acciones visibles; Campaigns de 19 a ≤4. Tras el merge con feature/campaigns-ux (Track VU, mismo día): suite 1320, i18n 885 cadenas 0 unfinished. Tras U7: suite 1322, i18n 890 cadenas 0 unfinished.
7vigprim. Track SC2 — Calendario del cielo y lunas de Júpiter (2026-09-17, ADR-040)
Plan: docs/PLANS/sky-calendar.md (con la tabla de validación). Rama propia feature/sky-calendar (nace de feature/ux-projects-campaigns).
El sistema solar se convierte en fuente de eventos: la pestaña «Sun & sky» sale de la barra (quedan cuatro: Tonight · Projects · Campaigns · Observatory, Ctrl+1..4) y todo su contenido se mueve intacto al diálogo de Herramientas «Calendario del cielo…», enriquecido con secciones nuevas: próximos eventos (60 días) en palabras llanas, calendario lunar, planetas, y las lunas de Júpiter esta semana. El motor (core/skyevents.py, 100 % local) cubre fases (sizigias verdaderas), perigeo/apogeo, conjunciones Luna–planeta y planeta–planeta, oposiciones y conjunciones solares por cruce de longitud eclíptica (la lección de Saturno 2026-10-04: elongación máx. 177.28° con β≈−2.7°), máximas elongaciones, eclipses probables (etiqueta honesta) y lluvias de meteoros. core/satellites.py añade los tránsitos de los galileanos y de sus sombras para la localización del usuario — IAU WGCCRE + calibración empírica (phase_cal_deg) — validado contra 22 ventanas Horizons q12, todo ≤10 min (peor 9.6), con la etiqueta «±10 min» visible. Titán queda documentado fuera de temporada (~2040). En Tonight, hasta 3 chips de eventos (lo grande primero, satélites solo si son observables, clic → el diálogo).
| Sub | Entregable | Estado |
|---|---|---|
| SC0 | motor skyevents + fix de longitud (Saturno) + 16 tests regresión | Hecho (f06573a + 46acd46) |
| SD | core/satellites.py + 21 tests + funcional, validación Horizons | Hecho (9060ca9) |
| SC1 | diálogo + contenido solar intacto + barra a 4 + tests retarget | Hecho (27fd9da) |
| SC2 | chips en Tonight + 5 tests | Hecho (4756416) |
| SC3 | i18n ES/EN (940 cadenas, 0 unfinished) + ADR-040 + revisión ADR-036 + esta sección | Hecho |
Estado: suite unitaria verde (1366). Fuera de esta iteración: afinado Horizons de los fenómenos de satélites (~1 min, spike primero); horas de contacto de eclipses; ocultaciones estelares; la línea de «impacto» sigue leyendo solo Luna+Kp (los eventos podrían alimentarla en v2).
7sexies. Rediseño features — acordeón nativo <details>, zigzag, hero banner (2026-09-17, ADR-039)
Motivación: la sección «Features» usaba masonry 2 columnas (.features-masonry + .feature-col + .feature-col--stagger) con 12 tarjetas que requerían scroll horizontal en móvil y no explicaban su propósito hasta hacer click en «Observe». El observador pidió: una columna, filas alternadas con zigzag, solo el titular visible (clic despliega todo), todas cerradas al cargar, hero fijo arriba (LCP intacto).
| Fase | Entregable | Estado |
|---|---|---|
| A | CSS zigzag/accordion en website/css/style.css (marca ADR-039, idempotente) | Hecho (2026-09-17) |
| B | ns_features_accordion.py walker anclado en id="feat-N" (12/12), balance 0, 13 imgs CLS-cero, i18n ×24 (sin claves nuevas), HTML → feature-list + 12 <details>/<summary> | Hecho (2026-09-17) |
| C | Validación contractual + idempotencia inyector + serve-check 200/0 404 + ADR-039 + anotación §7 | Hecho (2026-09-17) |
Decisiones pactadas (2026-09-17):
Acordeón nativo
<details>/<summary>(sin JS nuevo), caret▸/▾CSS::after(no i18n).Todas cerradas al inicio, varias abiertas a la vez.
Hero fijo como banner arriba (fuera del acordeón,
fetchpriority="high"eager).Zigzag: filas pares
margin-left: clamp(24px, 6vw, 96px)(móvil no rompe).<summary>reutilizafeat.N.title(misma clave i18n, 0 cadenas nuevas).JS
main.jsintacto; acordeón interno.feature-block.expandedsigue operando en el bloque abierto (capa interior).Balance 0 garantizado por walker anclado en
id="feat-N"único (12/12).
Validación contractual (2026-09-17):
feature-row12 ·nth-child(even)presente · balance 0 · i18n ×129 intactoServe-check: index 200 + 13 PNGs 200 + 0 404 en
127.0.0.1:8765Inyector idempotente (
ns_inject_screens.pyno re-toca capturas ni rompe balance)
cadenas 0 unfinished.
7vigbis. Track SF — Secuencias fotométricas y cartas de comparación (2026-09-21, ADR-042)
Motivación: el seguimiento de variables/HADS/SN es fotometría diferencial y su pregunta previa es «¿con qué comparo?»; hasta aquí la app la dejaba al usuario (protocolo comp_stars como texto libre, EFF con CNAME/CMAG/KNAME/KMAG=na). La herramienta web SecFot (González Farfán & González Carballo 2026) resuelve justo eso; el track la integra nativa (sin webview, con caché y tema propios), centrada en el proyecto: el campo nace del objetivo, nadie teclea coordenadas.
Decisiones pactadas (2026-09-21):
Sin paso nuevo en la máquina (plan/process/publish siguen solos): vive en la pestaña Seguimiento como botón primario junto a «Añadir visita» + línea de estado en palabras (ADR-038); el diálogo carga el peso.
Catálogo por defecto Gaia EDR3 (G), APASS DR9 (V) seleccionable; VSX (
B/vsx/vsxvía VizieR) cruza el campo y descalifica variables.Propuesta automática con criterios fotométricos (margen de brillo, |Δ(B−V)| ≤ 0,4, aisladas 10″, repartidas) y frase «por qué esta estrella».
Objetivo = centro marcado + ficha propia (retícula + nombre), jamás numerado como comp.
Fondo DSS2 por defecto o el FITS del propio usuario (WCS nativo o resuelto con
astrometry.pycomo blink; el original nunca se modifica; si el objetivo cae fuera del encuadre, aviso y caída a DSS2).Una sola vía de guardado: el worker no escribe nada; el diálogo confirma.
| Fase | Entregable | Estado |
|---|---|---|
| 1 | core/sources/vizier.py (asu-tsv Gaia/APASS/VSX, sonda de columnas + reintento -out.all, TTL 30 d), core/phototrans.py (Riello 2021, B−V con procedencia, clases bilingües), core/compstars.py (campo, cruce VSX 5″, propose_comps con razones, CSV), fixtures + 30 tests, ADR-042 + DATA_SOURCES | Hecho (2026-09-21) |
| 2 | core/field_math.py (TAN ida/vuelta, ticks, escala, label_layout), viz/finder_view.py (carta matplotlib bilingüe, fondo DSS2 o FITS), CLI nightscribe sequence, 19 tests | Hecho (2026-09-21) |
| 3 | Botón primario + línea de estado en Seguimiento, diálogo de opciones, SequenceWorker, secuencia al contexto del proyecto y protocol.comp_stars, EFF con comps reales, i18n ES/EN, 8 tests | Hecho (2026-09-21) |
| 4 | gui/widgets/finder_widget.py (ChartView sin matplotlib: hover con bandas/VSX, clic añade/quita Comp/Check) + gui/seqchart_dialog.py (tabla editable, export CSV/PNG, guardar en el proyecto), 16 tests | Hecho (2026-09-21) |
Punto de entrada (para quien retome):
Ampliaciones baratas: ATLAS Refcat2 como tercer catálogo (entrada nueva en
vizier.CATALOGS+describe_atlasenphototrans/compstars, el PS1→JC de Tonry 2012 ya está identificado en SecFot); el tipotransitpodría mostrar la carta desde su pestaña; tamaño del sensor en el perfil de cámara daría el campo por defecto exacto del encuadre del usuario.La carta PNG del CLI (
--fits) y la escena del widget comparten matemática (core/field_math.py): cualquier mejora visual va en ambos o en ninguno.Suite unitaria verde: 1461.
Calidad de las secuencias fotométricas (2026-09-29)
Por qué: una observación real del grupo ObSN (V0526 Per, 244 tomas de 40 s, sin WCS) salió desastrosa con el motor de serie: el campo derivó 134" en 2,9 h, la estrella salió de la apertura en menos de un minuto y la curva quedó en un rango de 8 magnitudes con errores de 0,5 mag. El diagnóstico completo (con las medidas antes y después) está en docs/PLANS/series-quality.md.
Qué se ha hecho (fases A, B y C del plan; ADR-048 revisado y ADR-054):
core/register.pyreescrito: se quita el cielo, las estrellas votan la transformación (traslación primero), la calidad es física (estrellas emparejadas y rms en px) y una rotación sólo entra si reduce el residuo real. Un frame que no se puede verificar hereda la anterior y se marcaalign_failed.La alineación está encendida por defecto (
align="auto"→coords: se mide en la rejilla nativa, la PSF nunca se remuestrea) y se persiste encfg_json.El punto cero se ata por comparada (
_tie_comps): una comp que entra y sale del campo o que satura ya no mueve la curva. La check nunca entra en el punto cero.La apertura sigue el seeing en serie y, para
variable/hads, el detrend por defecto es el mínimo de masa de aire.El motor dice lo que no sabe en el panel: deriva, residuo de las estrellas, comps que nunca entraron o están saturadas, y la ganancia que falta.
core/periodogram.py+viz/phase_view.py+gui/phase_dialog.py: búsqueda de período (Lomb-Scargle generalizado + PDM + ventana espectral, FAP por bootstrap), plegado y el informe de dos paneles, con ciclos cubiertos y avisos de alias. Se abre desde la ventana de la visita y desde la pestaña Medir del editor.
Números: 244/244 puntos medibles (antes 5), correlación 0.899 con la reducción independiente del observador (antes 0.32), residuo 0.0094 mag (antes 0.13), 0,23 s/frame (antes 4,6 s y sin converger), suite unitaria verde: 2105.
Fases siguientes (hechas el mismo día, ver ADR-048 rev.):
G1/G3 · La ganancia:
core/gain.pyla mide en las propias tomas (0.772 ± 0.002 e-/ADU en la serie real) y Ajustes gana los dos campos que nadie podía rellenar. El error interno de un punto pasa de inexistente a 0.0052 mag.A · La gráfica: escala robusta, zoom/paneo, barra = fotón y banda = sistemático (
err_internal, migración v13), banderas de dato separadas de los avisos de calibración, y controles en el bloque de serie.B · Robustez: la apertura sigue el seeing de verdad (los radios son los de la FWHM de referencia), bandera
seeingfrente acloud, y análisis robusto (recorte sobre la curva plegada; el recorte sobre la serie temporal se midió y se rechazó).C · Comparsas:
validate_on_platemide cada candidata en la placa abierta (saturación, linealidad, sensor, SNR) y el campo es el rectángulo real del sensor.D · La curva:
fetch_lightcurvede AAVSO, plegado conjunto, y la búsqueda acotada para que una década de historia no congele la ventana (de 60 s a 3-4 s).
Punto de entrada (para quien retome):
G2: el diálogo «Medir la ganancia…» (dos pares, con el ruido de lectura) sobre
core/gain.py, que ya hace el ajuste de dos niveles; sólo falta la ventana.seeingen el análisis: el desfase de punto cero por noche en el plegado multinoche (D3 del plan) sigue abierto.Deuda declarada: el paquete AAVSO de la curva no tiene test de red; su parseo (
_parse_curve) sí está cubierto por los tests de la cadena de prioridad.La fixture
tests/data/v0526per/(8 frames reales recortados, 4 MB) es la red de seguridad: no la sustituyas por datos sintéticos.
7novies. Interfaz 1.0: el hub de proyectos (2026-10-01, ADR-055)
Motivación: la app nació con Tonight como raíz y creció hasta ser un gestor de proyectos; el orden se había invertido. Interfaz 1.0 pone el proyecto en el centro, sin tocar el interior de ningún widget.
| Fase | Entregable | Estado |
|---|---|---|
| 0 | Mockup navegable docs/mockups/interface-1.0.html (validado) | Hecho (2026-10-01) |
| 1 | Shell: pestaña vertical «PROYECTOS» + QStackedWidget (Home · Tonight · Campaigns · Detalle), drawer superpuesto, arranque en Home sin red | Hecho (bdb69cc) |
| 2 | Bienvenida = asistente en línea (welcome_tab.ui + welcome_setup.py), decisión de vista inicial, gate bloqueante del paso Datos una vez por versión | Hecho (18fd38b) |
| 3 | Tonight bajo demanda (_maybe_compute_tonight), buscador embebido (new_project_bar.py) y formulario manual (manual_object_panel.ui) | Hecho (a256ffe) |
| 4 | UFE embebido: UfeDialog de QDialog a QWidget, página del shell con barra de retorno y shutdown() al salir | Hecho (bf3082e) |
| 5 | Avisos en Home: bandas «Qué pasa en el cielo» y «Toca revisitar»; campañas como tira del hub | Hecho (3b51ba5) |
| 6 | ADR-055, enmiendas a ADR-019/044, i18n ES/EN (2058, 0 sin terminar), docs | Hecho |
Punto de entrada (para quien retome): gui/main_window.py (_build_shell, _show_welcome, _ufe_page) y gui/widgets/ (welcome_setup.py, new_project_bar.py, manual_object_panel.py). Pendiente de pulir: las campañas aún conservan su vista propia (la tira de Home solo enlaza); el resto de diálogos (Ajustes, Calendario del cielo, Diario) siguen siendo ventanas.
7decies. Navegación con historial (2026-10-01, ADR-056)
Motivación: tras Interfaz 1.0 no había «atrás» global; el usuario pidió regresar siempre al punto anterior. Se añade una pila de ubicaciones (vista, proyecto, pestaña, campaña), una barra de navegación con migas (gui/main_window.py + ui/main_window.ui), atajos Alt+←/→/Home, botones laterales del ratón y una entrada permanente a Bienvenida (barra, Ayuda y drawer). Crear un proyecto reemplaza Tonight; el gate de actualización bloquea «Atrás»; action_log pasa a Ayuda.
Punto de entrada: navigate/back/forward/home/_apply_location en gui/main_window.py; tests en tests/unit/test_navigation_history.py.
7duodecies. Interfaz 1.4: el rediseño de Bienvenida (2026-10-01, ADR-055 rev.)
Motivación: Bienvenida funcionaba pero no convencía. El héroe era un degradado con medio panel vacío, el paso de objetivos un muro de casillas y los formularios pedían una latitud sin devolver nunca nada, así que escribir el primer dato no tenía recompensa.
| Pieza | Entrega | Estado |
|---|---|---|
| Héroe | assets/welcome_sky.svg (dibujo propio) + gui/widgets/welcome_sky.py, que pinta la Luna real de esta noche desde ephem_minor.moon(jd) y hace parpadear unas estrellas | Hecho |
| «Tu noche, ahora mismo» | Banda viva, 100 % local (coords.tonight_window, ephem_minor.moon/planet): ventana de oscuridad, fase y puesta de Luna, planetas al anochecer; se recalcula con cada cambio de sitio | Hecho |
| Rail | El conector del stepper se rellena y los pasos hechos llevan marca | Hecho |
| Objetivos | Tarjetas por tipo con KIND_COLORS, clicables enteras, resumen a un vistazo y el texto completo en el tooltip (_setup_kinds(..., card=True)) | Hecho |
| Datos | Recibo con una marca por línea | Hecho |
| CTA | Subtítulo y «Explorar primero» (bloqueado mientras una actualización esté sin confirmar) | Hecho |
| Movimiento | Preferencia ui_animations (Ajustes ▸ Interfaz), fundido de entrada y temporizador parado cuando no se ve | Hecho |
Punto de entrada: gui/widgets/welcome_setup.py (banda de noche, rail, tarjetas, fundido), gui/widgets/welcome_sky.py (el héroe pintado), gui/wizard.py (modo tarjeta de _setup_kinds, recibo de _setup_data), gui/ui/welcome_tab.ui y el bloque de Bienvenida de gui/theme.py. Una primera ejecución aterriza ahora en el paso del observatorio; la página cabe en una ventana de 1280x860 y un scroll exterior cubre las pantallas cortas.
7terdecies. Interfaz 1.5 bis: el sitio, en el mapa (2026-10-02, ADR-055 rev.)
Motivación: el paso del observatorio pedía una latitud a quien sabe dónde vive pero no sus coordenadas, y en una actualización la pantalla seguía diciendo «configura tu observatorio en tres pasos» a un veterano.
| Pieza | Entrega | Estado |
|---|---|---|
| El mapa | gui/widgets/site_map.py: equirectangular, clic para fijar, rueda para acercar, arrastre para moverse, terminador día/noche en vivo (efemérides locales), marcador y lectura bajo el cursor. Proyección cover, sin bandas laterales | Hecho |
| Datos del mapa | assets/world_map.json (costas y fronteras Natural Earth 110m, dominio público) | Hecho |
| Nombrar el punto | core/places.py + assets/world_places.json (1251 ciudades, Natural Earth 50m): la más cercana a menos de 250 km; si no, las coordenadas | Hecho |
| Ajustes | «Elegir en el mapa…» abre gui/site_map_dialog.py, dimensionado a una porción de la pantalla (antes abría a su sizeHint, un mapa de 300x150) | Hecho |
| Nombres | Se escriben siempre: al resolver un MPC, al detectar la ubicación y al clicar el mapa | Hecho |
| Aviso de versión | La insignia pasa junto a «Tus datos, a salvo»; el héroe explica la actualización, el rail marca 1 y 2 como hechos y la única acción vive con el informe | Hecho |
Punto de entrada: gui/widgets/site_map.py (el mapa y su proyección), core/places.py (nombrar), gui/site_map_dialog.py (el diálogo de Ajustes), gui/widgets/welcome_setup.py (set_context, _map_picked) y el paso del observatorio de gui/ui/welcome_tab.ui.
7quaterdecies. Interfaz 1.6: la lista y el proyecto, juntos otra vez (2026-10-02, ADR-055 rev.)
Motivación: separar la lista y el proyecto en dos páginas de la pila costó más de lo que aportó. Abrir un proyecto se llevaba por delante la lista, cambiar de proyecto era una ida y vuelta, y la pantalla de la lista (filas de 74 px, casi vacías) no decía gran cosa.
| Pieza | Entrega | Estado |
|---|---|---|
| Una página | VIEW_PROJECTS: lista y proyecto lado a lado con un QSplitter. VIEW_HOME/VIEW_DETAIL son dos nombres del mismo índice; el historial los distingue por el pid | Hecho |
| Ancho de la lista | Arrastrable y recordado (projects_list_width, 260–560); doble clic en el separador lo restablece y « la pliega | Hecho |
| Filas densas | 54 px, el color del tipo como borde izquierdo, selección teñida con el color del objeto, los números del objeto en la segunda línea, y la fila suelta lo que no cabe | Hecho |
| Barra del cielo | gui/widgets/sky_bar.py en la fila de navegación: la Luna con su fase real, la ventana de oscuridad, los planetas y los chips de eventos, en todas las vistas | Hecho |
| Panel en reposo | gui/widgets/night_panel.py: la noche (mismo cielo pintado que Bienvenida) y el camino a un proyecto nuevo | Hecho |
| Una sola fuente | core/night_brief.py: los números y las frases de la noche, compartidos por las tres superficies | Hecho |
| Encabezado | Campañas pasa a botón del encabezado; los ~105 px de las dos franjas vuelven a la lista | Hecho |
Punto de entrada: _build_shell (el separador y la página fusionada), _sync_projects_pane, _restore_list_width / _apply_list_width en gui/main_window.py; gui/widgets/project_row.py, sky_bar.py, night_panel.py y core/night_brief.py.
7quindecies. Interfaz 1.7: las pestañas del proyecto dejan de hacer scroll (2026-10-02, ADR-041 rev.)
Motivación: la Ficha y Análisis se habían salido de la pantalla. Con un proyecto con una visita, sus tomas y una curva medida, la ficha pedía 499 px y Análisis 1058 en un viewport de 537, y la leyenda de la curva se imprimía encima de las etiquetas del eje.
| Pieza | Entrega | Estado |
|---|---|---|
| Cromo | Los cuatro botones pequeños y la línea de contexto suben al masthead; la caja "Siguiente" es una banda fina; una página ya no repite el nombre de su pestaña (195 -> ~120 px en las cuatro) | Hecho |
| Análisis | Visitas y curva una al lado de la otra (maestro-detalle), con la lista ajustada a su contenido en vez de una caja fija | Hecho |
| Gráficas | ChartView.sizeHint() (640x260) en vez del tamaño de la escena, y la geometría de la leyenda de la curva derivada de su propia fuente | Hecho |
| Ficha | El bloque de parámetros lleva su "A fondo" en la cabecera, la tabla mide lo que miden sus filas y los chips dejan de estirarse | Hecho |
| Criterio | tests/unit/test_project_tabs_layout.py: ninguna de las dos pestañas pide más que el viewport a 1360x940 y 1360x860 | Hecho |
Punto de entrada: _section_layout, _build_analysis_tab, _analysis_curve_block en gui/main_window.py; gui/widgets/base_chart.py, gui/widgets/lightcurve_widget.py, gui/widgets/visits_panel.py, gui/overview.py y el masthead de gui/ui/projects_tab.ui.
Pulido (2026-10-02, mismo día). Dos cosas que la primera pasada hizo mal:
La ventana de visita tapaba el taller. Es un diálogo no modal con padre, así que el gestor de ventanas la mantiene por encima de la ventana principal; con el UFE ya como página, abrir una placa desde una visita dejaba el editor detrás. Ahora se aparta mientras el taller está en pantalla y vuelve al salir (
_ufe_enter/_ufe_leave). Ver ADR-045.El listado no podía mostrar la curva. La miniatura vivía en su propia columna a la derecha y le robaba 106 px a la línea donde van los números del objeto, así que con el ancho por defecto no cabían juntos. Ahora va en la primera línea, junto al nombre; los números aparecen cuando caben de verdad (medidos con la fuente real, nunca recortados) y el ancho por defecto de la lista es 500, donde caben la curva y los números.
7septendecies. Interfaz 1.8: la noche, dibujada en las pestañas del proyecto (2026-10-02, ADR-041 rev.)
Motivación: las tres pestañas del proyecto eran correctas y no contaban nada de un vistazo, la misma queja que tenía Bienvenida antes de la Interfaz 1.4.
| Pieza | Entrega | Estado |
|---|---|---|
| La banda | gui/widgets/night_ribbon.py: crepúsculo → amanecer con el arco de altura del objeto, la Luna con su fase real, el marcador de "ahora" y bloques de color sobre la línea de tiempo. Un widget, tres lecturas | Hecho |
| Ficha | Dos columnas (parámetros 60% / banda + gráficos 40%), la tabla como lista de definición, los hechos en una línea y "A fondo" activado por defecto | Hecho |
| Captura | Un panel "Telescopio y cámara" en vez de tres cajas, el estado en una fila y el plan dibujado sobre la noche con su veredicto | Hecho |
| Análisis | Las tomas de la visita dibujadas en la noche en que se hicieron, encima de las visitas | Hecho |
| Color | El chip de conexión (verde/ámbar) y los valores de estado tintados por lo que significan | Hecho |
| Criterio | Las cuatro pestañas caben en el viewport a 1360x940 y 1360x860, Captura incluida con CCDciel conectado | Hecho |
Punto de entrada: gui/widgets/night_ribbon.py (la banda), _plan_ribbon_refresh y _analysis_ribbon_refresh en gui/main_window.py, _refresh_ribbon en gui/overview.py.
7duodevicies. La ficha en dos columnas y la ficha como aterrizaje (2026-10-02, ADR-057 rev. + ADR-041 rev.)
Motivación: el dosier apilaba los parámetros y, debajo, los gráficos; y abrir un proyecto aterrizaba en Captura (el objetivo de la tarjeta Siguiente) en vez de en la ficha que dice qué es el objeto.
| Pieza | Entrega | Estado |
|---|---|---|
| La fila | Parámetros a la izquierda y gráficos a la derecha, 50/50 (row_body); la banda de la noche sigue a todo el ancho encima y el CTA debajo | Hecho |
| El colapso | Las columnas son widgets y siguen a su bloque: sin gráficos los parámetros ocupan todo el ancho, y al revés | Hecho |
| El apilado | Por debajo de 660 px de panel la fila se vuelve vertical (los gráficos tienen 300 px de mínimo) | Hecho |
| El 50/50 | Las etiquetas de las SectionCard envuelven: el mínimo del grid baja de 448 a ~180 px, que es lo que el reparto igual necesitaba | Hecho |
| El aterrizaje | Abrir un proyecto aterriza siempre en la ficha; el botón «Ir» sigue saltando al paso y el doble clic sigue yendo al trabajo. El refresco en sitio (land="keep") y la máquina de pasos (land="next") conservan lo suyo | Hecho |
Punto de entrada: gui/ui/object_panel.ui (row_body), _sync_body_columns y _apply_body_direction en gui/overview.py, _build_project_page en gui/main_window.py.
7undevicies. La Luna del héroe, y un observatorio que se ve (2026-10-02, ADR-055 rev.)
Motivación: el observador avisó de que la fase de la Luna se veía mal en Bienvenida y en Mis proyectos, y pidió que el dibujo incluyera un observatorio.
| Pieza | Entrega | Estado |
|---|---|---|
| La Luna | gui/moon_icon.py tenía la convención de signo invertida, así que la barra del cielo, la banda de la noche, Tonight y el calendario pintaban la fase espejada mientras el pintor del héroe la pintaba bien: dos lunas en una pantalla. Corregido, y un test nuevo compara los dos pintores a la misma elongación | Hecho |
| Los crecientes | La foto empaquetada es una luna llena con el limbo izquierdo oscuro y los mares de ese lado: un creciente menguante salía ~5 veces más apagado que uno creciente (0,7% frente a 2,5% del disco a -30/+30). Un lavado plano sobre la zona iluminada los iguala | Hecho |
| El observatorio | Estaba en vector en welcome_sky.svg y el encaje «cover» se lo comía (solo sobrevivía el canto de la cúpula en un héroe de 820x170 o 1290x206). El SVG es cielo puro ahora y la cúpula la pinta welcome_sky._paint_observatory, dimensionada con el héroe, abajo a la derecha bajo la Luna y con la lámpara cálida en la ranura | Hecho |
Punto de entrada: gui/moon_icon.py (el signo y el lavado), gui/widgets/welcome_sky.py (_observatory_geometry, _paint_observatory), assets/welcome_sky.svg (sin observatorio ni suelo).
7vicies. La galaxia, el icono y la invitación (2026-10-03, ADR-026 rev. + ADR-055 rev.)
| Pieza | Entrega | Estado |
|---|---|---|
| La galaxia | La banda eran cinco elipses de borde duro (escalones) con la misma densidad de estrellas que el cielo vacío (19% de las estrellas en el 20% del lienzo, tres en su núcleo). Ahora: brillo por degradado radial, la Gran Grieta como cadena de elipses oscuras, nubes estelares y 600 estrellas de granulación en el sistema de la banda (3x la densidad de fuera). Sin filtros SVG: un grupo desenfocado costaba 13,6 ms de 15; con degradados, 2,8 | Hecho |
| El icono | appicon.svg era oro sobre la teja azul, fuera de la paleta (el choque se veía junto a la marca azul de Bienvenida). Repintado con el azul de acento y núcleo casi blanco en la estrella, y regeneradas las diez medidas + el .ico + el .icns con QtSvg + Pillow. Guarda: todo color del icono es neutro o frío | Hecho |
| La invitación | El panel en reposo lo dice sobre el dibujo: «Selecciona un proyecto» / «Verás su ficha, su plan y su curva. O crea uno nuevo.», pintado en la mitad izquierda del cielo, sin comer clics, con el botón de crear debajo | Hecho |
Punto de entrada: assets/welcome_sky.svg (la banda), assets/appicon.svg + installer/ (el icono), gui/ui/night_panel.ui + gui/widgets/night_panel.py (la invitación).
7undecies. Los avisos se disuelven en el listado (2026-10-01, ADR-055 rev.)
Motivación: el panel «Necesita tu atención» y la banda «Toca revisar» repetían lo que cada fila ya dice (próxima acción en palabras, tinte de urgencia, edad de la última visita). Se retiran ambos; Home queda cabecera + banda del cielo
lista + tira de campañas. Además, con la lista en pantalla el drawer lateral
no aporta: la pestaña vertical PROYECTOS se oculta en Home y solo aparece en las demás vistas. Las flechas Atrás/Adelante de la barra ganan tamaño, y el botón «Volver» del UFE se retira (lo cubre la pila general).
Punto de entrada: gui/main_window.py (_build_shell, _update_vtab_visibility, _drawer_open); tests en tests/unit/test_shell_real_case.py y test_projects_hub.py.
7unvicies. Interfaz 1.9: el equipo, un paso más, y el código clásico se retira (2026-10-07, ADR-055 rev. + ADR-044 rev.)
Motivación: la Bienvenida tenía tres pasos y le faltaba el que decide la escala de la imagen; y el UFE llevaba meses siendo el camino normal mientras los tres diálogos clásicos y su interruptor seguían en el código «por si acaso».
| Pieza | Entrega | Estado |
|---|---|---|
| El paso de equipo | Bienvenida pasa a cuatro pasos (dónde estás, equipo, objetivos, datos). El paso nuevo pide apertura, píxel, focal, tipo de cámara y un preset de cámara, y NO bloquea: viene con los valores de la app | Hecho |
| La escala contesta | La tira «Tu escala, ahora» calcula los arcsec por píxel y dice si el muestreo encaja con el seeing de un sitio típico (exposure.sampling), el mismo gancho que «Tu noche, ahora» | Hecho |
| El binning | plate_scale aplica el binning del config: un 2x2 da escala doble, y sus lectores (planificador, exposición NEO, ASTAP, EXOTIC, campo de visión, CLI) dejan de estar desviados por ese factor | Hecho |
| La limpieza | Fuera el diálogo clásico de blink, SnAnnotateDialog, la carta clásica con su SequenceWorker y el interruptor ufe_default: el UFE queda como única puerta. Unas 2.000 líneas menos | Hecho |
| El asistente modal | Fuera wizard.ui, maybe_run_wizard, _build y _page_ok (código muerto de la Interfaz 1.0); los ayudantes que sí se usan se quedan compartidos en gui/wizard.py | Hecho |
Punto de entrada: gui/ui/welcome_tab.ui, gui/widgets/welcome_setup.py, gui/wizard.py, core/exposure.py, core/cameras.py; la limpieza en gui/main_window.py, gui/workers.py y gui/ui/settings_dialog.ui; tests en test_welcome_setup.py, test_wizard_site.py, test_exposure.py y test_ufe_integration.py.
7duodetricies. La guía y la web: una fuente, dos renderizados (2026-10-08, ADR-070)
Motivación: la guía de usuario vivía solo en docs/user (dentro de la app) y la carpeta website/ era una landing escrita a mano, con otra paleta y un taller de capturas del shell retirado: contaba una historia distinta de la de la guía. Se pidió una web clara, en el estilo de la aplicación, que explique qué es el software y qué permite hacer, y que sea además la guía.
| Pieza | Entrega | Estado |
|---|---|---|
| El generador | website/tools/build_site.py: convierte el README y docs/user en HTML, inyecta la paleta del tema y los chips del catálogo, y es idempotente | Hecho |
| La landing | Una página por idioma con el héroe (el cielo de la app), la definición del README, los tipos como tarjetas, el detalle en acordeón y el índice de capítulos | Hecho |
| La guía | Una página por capítulo y idioma, con barra lateral, anterior/siguiente, anclas por encabezado y las cajas «Why …?» como callouts | Hecho |
| El guardián | tests/unit/test_site_build.py: el conversor, la regeneración byte a byte, los enlaces internos y que la paleta salga del tema | Hecho |
| En el programa | Ayuda → Guía de usuario (web), en el navegador del sistema; el visor interno sigue siendo la puerta sin red | Hecho |
| La publicación | .github/workflows/pages.yml construye la web y la publica en GitHub Pages (push a main o release/v0.1, y manual) | Hecho |
Punto de entrada: website/tools/build_site.py, website/tools/site.css, website/tools/site.js, website/README.md; en la app, gui/main_window.py (on_guide_web) y gui/ui/main_window.ui; test en tests/unit/test_site_build.py.