NIGHTSCRIBE

NightScribe — Flujos guiados (UX v3)

English version

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ónOrigenEfecto
Magnitud límitelimit_mag en config (equipo)Híbrida (ADR-025): duro en SN/cometas/exoplanetas, aviso ⚠ mag>N en NEO/PCCP sin descartar
Horizonte localFichero TheSkyX del usuario (ADR-020)Altura mínima por azimut + margen de seguridad; fallback a min_alt plano
Lunaephem_minor (ADR-009)Aviso + penalización de score por separación/iluminación; no filtro duro
Viabilidad de sesióncore/exposure.py + horizonteLa ventana debe cubrir la duración de la sesión; se muestra «inicio seguro hasta HH:MM»
Escala de placaPerfil 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

  1. 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.

  2. 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.

  3. 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.

  4. 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

  1. 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.

  2. 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.

  3. 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.

  4. 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 — reutiliza enrich.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

FaseEntregableEstado
1ADR-019…022 + este documento + DESIGN actualizadoHecho (2026-08-24)
2Fundació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, testsHecho (2026-08-24)
3Modelo de proyecto: migración db (user_version 0→1), core/project.py, máquina de pasos, testsHecho (2026-08-24)
4GUI 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), i18nHecho (2026-08-24)
5Exportadores: core/sequence.py (NINA/CCDciel/CSV) + efemérides (CSV + TheSkyX + CdC validados en importación real), testsHecho (2026-08-24); CCDciel con formato real .targets (CONFIG v5) contra docs/ccdciel_sequence_sample.targets (2026-09-06)
6core/mpc_report.py + paso en flujo NEO + subcomando CLI project mínimo + polish, i18n y docs finalesHecho (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.

FaseEntregableEstado
ATema global oscuro: gui/theme.py (paleta + QSS cromo + KIND_COLORS), apply_theme(app) en app.py, ADR-026, tests/unit/test_theme.pyHecho (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íoHecho (2026-08-25)
CTabla «Lista completa» rediseñada sobre el tema, doble-click→proyecto, regeneración i18n ES/EN de las cadenas nuevas, tests de humoHecho (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):

  1. 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.)

  1. 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).

  2. Se extrae a gui/overview.py un panel compartido (ObjectPanel) que usan el

hub de Proyectos y el diálogo Explore… del menú Herramientas: una sola fuente de verdad.

  1. 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):

PiezaOrigen
Enrich (red, caché)gui/workers.py ExploreWorker → core/enrich
Frase de enganche, bulletscore/narrative.hook, core/narrative.fact_bullets
Parámetros + explicación ES/ENcore/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, horasproject.context (snapshot, gui/main_window.py:1474)
Clic en gráfico → zoom/exportgui/main_window.py:74 _ChartClickFilter
Idioma de pares ES/ENorbits.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).

FaseEntregableCriterio de aceptaciónEstado
D1gui/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/ENPanel construido offscreen; con e fake: hook visible, filas de parámetros con explicación; con {}: estado no encontradoHecho (2026-08-26) — gui/overview.py + tests/unit/test_overview_panel.py (9 tests offscreen, sin red)
D2ObjectPanel 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 widgetCon e fake: 4 slots de gráfico o los mensajes de ausencia correctos; sin e: vacíosHecho (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)
D3ObjectPanel 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 faltaCon ctx fake de neo: chips completos; de sn: sin tasa/exposición; no rompe con ctx vacíoHecho (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)
D4Integració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 proyectoSin red: seleccionar proyecto muestra estado cargando y no revienta; la máquina de pasos, plan y botones siguen intactos; tests de humo offscreenHecho (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_exploreEl diálogo Explore… del menú Herramientas idéntico en contenido; el paso 4 de NEO sigue abriéndolo pre-rellenadoHecho (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)
D6i18n + verificación + docs: lupdate/lrelease → nightscribe_{es,en}.ts/.qm (ADR-014); pytest tests/unit verde; esta sección con estados finalesCadenas nuevas traducidas ES/EN; tests de humo en tests/unit/test_overview_panel.py (D1-D3) + humo del hub (D4) en verdeHecho (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:

  1. Pestaña «Detalles» primero: nueva en projects_tab.ui (índice 0 de tabs_steps). La carta de presentación del objeto — ObjectPanel — vive dentro de ella (_get_proj_panel la 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).

  2. Adiós al gráfico «¿Dónde vive?»: viz/families_view.py eliminado (ya no tenía consumidores); core/post.build_charts deja de dibujarlo y CHART_LABELS pierde la entrada families. En el panel el slot pasa a ser orbital / cielo / campo / curva de luz (transitos de exoplanetas, ya cubierto por transit_view).

  3. Se omiten los huecos: los slots que build_charts no 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ño panel (1200×675, añadido a style.SIZES) para menos margen y más legibilidad en la ventana.

  1. Ajuste de resolución de gráficos: Ajustes > Gráficos del panel añade la opción Al redimensionar la ventana — Rápido (el gráfico se dibuja una vez al tamaño panel y 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 como config["chart_zoom"] ("scale" / "re-render") y ObjectPanel lo lee antes de cada llamada a build_charts; cambiar el ajustes con un panel en pantalla lo redibuja (rebuild_charts). (Retirado 2026-09-04: la opción chart_zoom y la pestaña Ajustes > Gráficos se eliminaron cuando llegaron los widgets vectoriales de ADR-029, que dibujan a resolución nativa. Ver docs/adr/ADR-028 y el plan docs/PLANS/approach-chart.md, Slice 0.)

  2. 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).

  1. 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 nuevo test_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/unit verde (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):

  1. Leer esta sección 7ter completa, en orden.

  2. Empezar por D1 (gui/overview.py), validar la base, y proseguir en orden D2 → D3 → D4 → D5 → D6.

  3. 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.

  4. Cabecera GPL obligatoria en todo .py nuevo, código en inglés, cadenas visibles en la GUI por self.tr() y pares ES/EN por orbits.pick (o tr() para los estáticos). Idioma activo: config.get("language") (patrón MainWindow._lang, gui/main_window.py:227).

  5. 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):

  1. Leer, en este orden: docs/adr/ADR-019, ADR-020, ADR-021, ADR-022 y este documento completo.

  2. 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 en coords.py, penalización Luna en suggest.py, integración en planner.py, silueta en viz/sky_view.py, config ampliada, mock tests/fixtures/horizon_sample.txt + tests. - Fase 3: migración core/db.py (user_version 0→1: tablas projects, 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.ui con 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_view con horizonte real, i18n (.ts/.qm) actualizado. - Fase 5: core/sequence.py (exportadores NINA JSON / CCDciel .targets / CSV genérico para secuencias de captura) y core/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 CLI nightscribe 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.py interpreta la exportación de límites de SkyX (docs/limits-sample.hrz = referencia canónica) además del texto az alt, con 16 tests de seguridad y rechazo de ficheros rotos. (E2) El horizonte válido manda: en Ajustes desactiva min_alt (tooltip explicativo) y horizon_margin_deg suma sobre él. El rango seguro es visible en toda la app (ADR-020): planner.safe_window_for calcula span seguro + best_time + latest_safe_start; sky_view somorea 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_ccdciel escribe 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á en docs/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.py compone 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 por self.tr(), red solo desde core/sources/ vía core/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):

  1. Un solo filtro en la cabecera (K1). El cmb_filter sale de grp_list y se instala en la fila de Tonight (junto a lbl_context y lbl_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.

  2. Lista blanca de tipos habilitados en Ajustes (K2). Se añade config["enabled_kinds"] (defecto = 6 tipos) y un grupo «Tonight: object kinds» en tab_observing con 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.

  3. core/ no se toca: toda la filtración vive en gui/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_n sigue diversificando; enabled_kinds solo corta lo que se muestra en la pestaña, nunca el scoring.

  4. 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 de KIND_LABELS / KIND_COLORS).

  5. 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 de tonight_kind, whitelist que recorta/crece el combo, fallback a «All» al deshabilitar el tipo activo. pytest tests/unit verde.

### 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):

  1. core/suggest.best_per_kind(scored, n): entrada ya puntuada y ordenada por score global (la que devuelve top_n); mantiene como máximo n de cada tipo (defecto config["best_per_kind_n"] = 5, configurable 1..50); n<=0 = sin tope. Devuelve (grid, best_ids): grid conserva el orden por score global (no agrupado por tipo), y best_ids = el id del mejor de cada tipo presente (el anillo).

  2. Grid «Esta noche» (_build_suggestion_grid): filtra por «visible» (K1+K2), aplica best_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 cambiar best_per_kind_n desde Ajustes.

  3. 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.

  4. Planner _comet_targets (bottleneck): las 15 ephémerides Horizons van en paralelo (pool de ~4 workers, los cobs siguen siendo los 15 más brillantes). n_comets=15 se conserva.

  5. Config: config["best_per_kind_n"] = 5 (nuevo, default 5) y ui/settings_dialog.ui tab_observing → fila spn_best_pk (SpinBox 1..50) con traducción ES/EN.

  6. 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.py actualizado — ahora toda fila del fixture lleva anillo (un por tipo ⇒ best-of-each); test_tonight_kinds.py actualizado — el pccp (rank 4 global) SÍ lleva anillo al cambiar al filtro «pccp» (y SÍ lo conserva en «All»). pytest tests/unit verde (345 tests, 13.1 s).

  7. Idiomas: 346 cadenas (2 nuevas), 0 unfinished.

Punto de entrada (para cualquier IA o humano que retome esta pantalla):

  1. Leer esta sección 7quater, ADR-019 (hub de pestañas) y ADR-026 (fuente única de temas).

  2. 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 en settings_dialog.ui + guardas en on_open_settings).

  3. 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).

  4. Cabecera GPL obligatoria en todo .py nuevo, código en inglés, cadenas visibles en la GUI por self.tr() (patrón de main_window.py), pares ES/EN por orbits.pick (o tr() para los estáticos). Idioma activo: config.get("language") (patrón MainWindow._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):

  1. Clic en fila → Explorar (ya era así; se mantiene).

  2. 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).

  3. 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.

  1. 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 del project_lookup que 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_content en gui/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 anterior project_action(name, fallback, continue_) se elimina). MainWindow reacciona 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.

  2. _create_project ahora retorna el dict creado (o None,

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).

  1. _goto_active_project(name, fallback=None) (nuevo, privado): busca el proyecto activo cuyo object_name coincide con name o con el id/name del fallback (los NEOCP/PCCP guardan el número MPC como id y un nombre provisional como name; cualquiera de los dos puede ser el que quedó en el proyecto), y selecciona en el hub. Devuelve True/False.

  1. El panel sigue siendo testeable: el ObjectPanel acepta project_lookup como callable inyectado (no importa core.db directamente — MainWindow._explore_panel lo 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):

  1. Leer esta sección, ADR-019 (hub) y ADR-026 (temas).

  1. Los 4 puntos de contacto con gui/: _card_button, _table_open_explore, _open_explore_dialog, _explore_panel (en main_window.py) y el constructor de ObjectPanel + _refresh_cta / _cta_clicked (en overview.py).

  2. No tocar core/project.py para 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).

  1. Cabecera GPL, código inglés, self.tr() en la GUI, pyside6-lrelease tras 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):

  1. Paquete gui/widgets/: ChartView base (zoom bajo el cursor, pan, fit, export PNG, hover) + OrbitChart (animación por fecha + hover r/ν/t) + SkyChart (hover hora/altura/azimut, ventana segura, mejor hora). core/orbit_math.py y core/sky_math.py guardan la matemática pura sin matplotlib (la comparten viz/* y gui/widgets/*); gui/widgets/palette.py aporta las constantes de color.

  2. chart_viewer.py dual-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().

  3. Slots del panel: órbita y cielo → OrbitChart/SkyChart vivos (zoom/pan/hover/animación). transit (curva de luz) y field (cutout SN) → QLabel+QPixmap (aún sin vectorial). build_charts (en core/post.py) sigue como "¿se puede?" y sigue produciendo los PNGs de redes.

  4. 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 e al 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):

  1. Leer ADR-029, ADR-010 (sección "Alcance") y docs/PLANS/qt-chart-widgets.md.

  2. 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.

  3. No importar matplotlib dentro de gui/widgets (regla del plan, probada por test). Las fuentes de datos viven en core/*.py (puras) y core/post.py (PNGs de redes, intacto).

  4. 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):

  1. 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).

  2. Tabla multilínea: wordWrap + auto-alto de fila que sigue a la columna elástica al redimensionar (ojo: sectionResized llega antes de que columnWidth() se actualice — el handler fuerza el ancho notificado).

  3. Tabla para SN: orbits.explain_transient (tipo, galaxia, distancia, brillo, descubrimiento; z a fondo).

  4. Ficha de tránsitos: bug arreglado — la rama exoplanet de enrich pierde el evento ExoClock; ahora se fusiona con el patrón ADR-027. Tabla orbits.explain_transit con inicio/fin UTC, profundidad (mmag y %), duración y veredicto telescopio mínimo vs. tu apertura.

  5. 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.

  6. «Discovered» exacto en NEOs y PCCP: SBDB discovery=1 (fallback first_obs), NEOfixer /orbit/ para NEOCP, PCCP mapea su propia columna. Pool paralelo de 4 + caché 7 días. Nuevo core/dates.py único.

  7. Filtro duro de apertura en tránsitos: transits_tonight(aperture_in=) + interruptor transit_scope_filter en 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):

  1. 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.

  2. Goto fresco en main_window.py: para neo/comet/pccp la acción del CcdcielWorker resuelve position_at antes 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.

  3. Ficha con época visible: _enrich_small_body pide 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.

  4. Cierre: i18n ES/EN (~4 cadenas), revisión de ADR-030, esta sección, pytest tests/unit verde (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.

SubEntregableEstado
A0Migración user_version 4: closed_at, outcome, tags, favorite + índice createdHecho
A1core/project.py: close()/reopen()/set_tags/set_favorite + OUTCOMES por tipoHecho
A2GUI: botones cerrar/reabrir con diálogo de resultado + asesor de cierre + confirmación de archivarHecho
A3Hub: agrupación por año, filtro por tipo, búsqueda, favoritos primero, orden configurableHecho
A4project_files visibles en Detalles + carpeta por proyecto + registro de blink/FITSHecho
A5CLI project close/reopen/files + i18n ES/EN (458 cadenas) + esta secciónHecho

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).

SubEntregableEstado
B0Migración user_version 5: project_sessions, session_images, photometry_points + core/followup.py CRUDHecho
B1core/fits_meta.py: DATE-OBS/FILTER/EXPTIME del header + MJDHecho
B2Pestaña «Seguimiento» en proyecto SN (visitas, apilados, notas, cadencia)Hecho
B3Entrada fotometría sin fricción: rápida (solo magnitud) + pegado tolerante (AIJ/Tycho/CSV) + ficheroHecho
B4Curva de luz: PNG + widget QGraphicsView + plantillas típicas esquemáticas (Ia, II-P/L, Ib/c, SLSN, kilonova)Hecho
B5core/series.py: motor de series — apertura, detección, ensemble automático, Δmag, análisis de campaña con veredicto vs plantillaHecho
B6Animación de evolución: N frames alineados por afín desde WCS, GIF/MP4Hecho
B7Didáctica SN: tipos ampliados + TNS enrich cuando SIMBAD no conoce la SNHecho
B8Captura SN: recommended_sn_exposure(mag) + secuencias multi-filtro retrocompatibles + bloque SN en Plan tabHecho
B9Post vivo: curva de luz en build_charts + etiquetas de animaciónHecho
B10FITS anotado: copia con keywords NS_ en cabecera (Python puro, sin astropy)Hecho
B11Cadencia con memoria: «hace N noches» en pestaña Seguimiento + chip en TonightHecho
B12Contexto de surveys ASASSN/ZTF (opcional)Pendente (opcional)
B13Cierre: i18n ES/EN (467 cadenas), ADRs, esta secciónHecho

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.

SubEntregableEstado
C0Aná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_filesHecho
C1viz/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 configurableHecho
C2Sin 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ónHecho

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.

SubEntregableEstado
0core/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-transitHecho
1suggest: 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ácticaHecho
2Bloque 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.dataHecho
3Export con ventana: CCDciel StartTime/EndTime + MandatoryStartTime=True (StartRise off — best-effort hasta validar contra el CCDciel real), NINA metadata, CSV con start_time por frameHecho
4Handoff 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/AAVSOHecho
5Cierre: 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ónHecho

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.

SubEntregableEstado
P1Migració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.pyHecho
P29 call sites de main_window.py escribiendo vía project.storage_dir() + CLI project show imprime folder:Hecho
P3Ajustes > 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 + ampliadosHecho
P4Cierre: i18n ES/EN (598 cadenas, 11 nuevas), lrelease, ADR-032, esta secciónHecho

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).

SubEntregableEstado
H0.1–H0.5Lector XLSX stdlib, caché TTL 12 h, parseo de colores + cobertura, core/hads.py (catálogo/merge/derivados/diente de sierra), docs de datosHecho
A.1–A.3Fase hads en el planner (gate 1 ciclo, sesión 2P en _visibility), scoring (4 familias), fragmentos ES/ENHecho
B.1–B.4GUI listable (color magenta, icono, columnas, fase), config + migración amable + combos, enrich (antes de la regex exoplaneta), ficha con explain_hads + chipsHecho
C.1–C.3Proyectos hads (reported_aavso), narrativa ES/EN citando a Wils/VVS/VSX, post/tuitHecho
D.1–D.5Bloque 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).

SubEntregableEstado
V0.1–V0.9core/variables.py, core/campaign.py, core/sources/vsx.py (TTL 7 d), core/sources/surveys.py (ALeRCE ZTF, TTL 30 d), migración v7Hecho
VA.1–VA.4Fase planner campaigns (vencidas + visibilidad), sub-dict variable, scoring de campaña, fragmentos ES/ENHecho
VB.1–VB.6GUI listable variable, ficha VSX→SIMBAD→manual, plegado por época, contextosHecho
VC.1–VC.10Proyectos variable, gestor de campañas (crear/editar/finalizar, objetivos, adjuntar), hub por campaña, Follow-upHecho
VD.1–VD.9Chip 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 plegadaHecho
VE.1–VE.2i18n 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).

SubEntregableEstado
U0.1–U0.6tanda de correcciones y feedbackHecho
UA.1–UA.7pestaña Campaigns + enlacesHecho
UB.1–UB.3lenguaje de gestosHecho
UD.1–UD.5proyecto como página única + pestaña ObservatoryHecho
UC.1–UC.2i18n + 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):

  1. Señales y campañas — docs/adr/ADR-037-campaign-signals.md + plan docs/PLANS/signals-campaigns.md. Ejecutado completo el 2026-09-16 (SC1–SC5): ver la sección 7septdecies más abajo.

  2. Diario & Divulgación — docs/adr/ADR-036-journal-and-sunsky.md + plan docs/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.

SubEntregableEstado
SC1–SC3triple condición + consola de señales + chip de extremoHecho (fcdf443, f96b2e6, 50bdf58)
SC4avigilias: core/vigils.py, fase planner, fusión, chip 👁, consola solo-caché, editor en AjustesHecho (24 tests test_vigils.py)
SC4bcanal AAVSO: spike (Discourse JSON + tabla de campañas viables), core/sources/aavso.py, fase planner, fusión, chip 📣, checkbox en AjustesHecho (17 tests test_aavso.py con títulos reales del spike)
SC5cierre: i18n ES/EN (809 cadenas, 0 unfinished), revisión ADR-035 (rol de la pestaña), esta secciónHecho

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).

SubEntregableEstado
J0–J2Diario en el menú + vista derivada + diálogo/CLIHecho
J3re-cableado score/✔ a actividad de proyectosHecho
S1–S3«Sol y cielo» + impacto + PNG redes + post del cieloHecho

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=True siempre; 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 filas source LIKE 'survey:%' (jamás paste/manual/file/quicklook) y solo elimina las survey:% huérfanas si el resultado es no vacío: una respuesta vacía nunca borra fotometría almacenada. Solo proyectos sn/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»); proyecto sn con sn_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ía viz/lightcurve_view.py) y overview.py comparten la construcción (puntos, prioridad de sn_type, pico, plegados, diente de sierra); el crash TypeError cuando min era None (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.py ya no confunde enteros YMD con MJD (20260909 → 61292 en vez de 58107), acepta JD (restando 2 400 000,5) y MJD directo, y devuelve None si no sabe (sin excepción).

SubEntregableEstado
VU.1parser pegado YMD/MJD/JD + reparación de filas pid 18 + testsHecho
VU.2fetch_points_detailed + worker + informe ok/empty/errorHecho
VU.3upsert_survey_points (idempotente, protegido, con puerta de kind) + testsHecho
VU.4curva inline (etiquetas, toggle de plantilla, líneas de conexión) + build_payload compartidoHecho
VU.5aviso de pegado + ampliación ADR-035 + esta secciónHecho

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.

SubEntregableEstado
U1cimiento Projects: Filters ▸ + ⋯ gestión; fuera Refresh/Campaigns…/pieHecho (35f5a34)
U2core/attention.py + dashboard + filas ricas + sparklineHecho (6145880)
U3Next card centro de mando + bloques colapsados + CCDciel → ObservatoryHecho (1a08e2e)
U4Follow-up despejado (⋯ herramientas + bloque colapsado)Hecho (0b5f5b3)
U5Campaigns sala de guerra + ayudas ⓘ + «Close»Hecho (a0e09f7)
U6lenguaje llano + i18n ES/EN (876 cadenas) + ADR-038 + esta secciónHecho
U7la 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).

SubEntregableEstado
SC0motor skyevents + fix de longitud (Saturno) + 16 tests regresiónHecho (f06573a + 46acd46)
SDcore/satellites.py + 21 tests + funcional, validación HorizonsHecho (9060ca9)
SC1diálogo + contenido solar intacto + barra a 4 + tests retargetHecho (27fd9da)
SC2chips en Tonight + 5 testsHecho (4756416)
SC3i18n ES/EN (940 cadenas, 0 unfinished) + ADR-040 + revisión ADR-036 + esta secciónHecho

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).

FaseEntregableEstado
ACSS zigzag/accordion en website/css/style.css (marca ADR-039, idempotente)Hecho (2026-09-17)
Bns_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)
CValidación contractual + idempotencia inyector + serve-check 200/0 404 + ADR-039 + anotación §7Hecho (2026-09-17)

Decisiones pactadas (2026-09-17):

  1. Acordeón nativo <details>/<summary> (sin JS nuevo), caret ▸/▾ CSS ::after (no i18n).

  2. Todas cerradas al inicio, varias abiertas a la vez.

  3. Hero fijo como banner arriba (fuera del acordeón, fetchpriority="high" eager).

  4. Zigzag: filas pares margin-left: clamp(24px, 6vw, 96px) (móvil no rompe).

  5. <summary> reutiliza feat.N.title (misma clave i18n, 0 cadenas nuevas).

  6. JS main.js intacto; acordeón interno .feature-block.expanded sigue operando en el bloque abierto (capa interior).

  7. Balance 0 garantizado por walker anclado en id="feat-N" único (12/12).

Validación contractual (2026-09-17):

  • feature-row 12 · nth-child(even) presente · balance 0 · i18n ×129 intacto

  • Serve-check: index 200 + 13 PNGs 200 + 0 404 en 127.0.0.1:8765

  • Inyector idempotente (ns_inject_screens.py no 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):

  1. 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.

  2. Catálogo por defecto Gaia EDR3 (G), APASS DR9 (V) seleccionable; VSX (B/vsx/vsx vía VizieR) cruza el campo y descalifica variables.

  3. Propuesta automática con criterios fotométricos (margen de brillo, |Δ(B−V)| ≤ 0,4, aisladas 10″, repartidas) y frase «por qué esta estrella».

  4. Objetivo = centro marcado + ficha propia (retícula + nombre), jamás numerado como comp.

  5. Fondo DSS2 por defecto o el FITS del propio usuario (WCS nativo o resuelto con astrometry.py como blink; el original nunca se modifica; si el objetivo cae fuera del encuadre, aviso y caída a DSS2).

  6. Una sola vía de guardado: el worker no escribe nada; el diálogo confirma.

FaseEntregableEstado
1core/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_SOURCESHecho (2026-09-21)
2core/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 testsHecho (2026-09-21)
3Botó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 testsHecho (2026-09-21)
4gui/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 testsHecho (2026-09-21)

Punto de entrada (para quien retome):

  • Ampliaciones baratas: ATLAS Refcat2 como tercer catálogo (entrada nueva en vizier.CATALOGS + describe_atlas en phototrans/compstars, el PS1→JC de Tonry 2012 ya está identificado en SecFot); el tipo transit podrí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.py reescrito: 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 marca align_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 en cfg_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.py la 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 seeing frente a cloud, y análisis robusto (recorte sobre la curva plegada; el recorte sobre la serie temporal se midió y se rechazó).

  • C · Comparsas: validate_on_plate mide 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_lightcurve de 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):

  1. 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.

  2. seeing en el análisis: el desfase de punto cero por noche en el plegado multinoche (D3 del plan) sigue abierto.

  3. 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.

  4. 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.

FaseEntregableEstado
0Mockup navegable docs/mockups/interface-1.0.html (validado)Hecho (2026-10-01)
1Shell: pestaña vertical «PROYECTOS» + QStackedWidget (Home · Tonight · Campaigns · Detalle), drawer superpuesto, arranque en Home sin redHecho (bdb69cc)
2Bienvenida = asistente en línea (welcome_tab.ui + welcome_setup.py), decisión de vista inicial, gate bloqueante del paso Datos una vez por versiónHecho (18fd38b)
3Tonight bajo demanda (_maybe_compute_tonight), buscador embebido (new_project_bar.py) y formulario manual (manual_object_panel.ui)Hecho (a256ffe)
4UFE embebido: UfeDialog de QDialog a QWidget, página del shell con barra de retorno y shutdown() al salirHecho (bf3082e)
5Avisos en Home: bandas «Qué pasa en el cielo» y «Toca revisitar»; campañas como tira del hubHecho (3b51ba5)
6ADR-055, enmiendas a ADR-019/044, i18n ES/EN (2058, 0 sin terminar), docsHecho

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.

PiezaEntregaEstado
Héroeassets/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 estrellasHecho
«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 sitioHecho
RailEl conector del stepper se rellena y los pasos hechos llevan marcaHecho
ObjetivosTarjetas por tipo con KIND_COLORS, clicables enteras, resumen a un vistazo y el texto completo en el tooltip (_setup_kinds(..., card=True))Hecho
DatosRecibo con una marca por líneaHecho
CTASubtítulo y «Explorar primero» (bloqueado mientras una actualización esté sin confirmar)Hecho
MovimientoPreferencia ui_animations (Ajustes ▸ Interfaz), fundido de entrada y temporizador parado cuando no se veHecho

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.

PiezaEntregaEstado
El mapagui/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 lateralesHecho
Datos del mapaassets/world_map.json (costas y fronteras Natural Earth 110m, dominio público)Hecho
Nombrar el puntocore/places.py + assets/world_places.json (1251 ciudades, Natural Earth 50m): la más cercana a menos de 250 km; si no, las coordenadasHecho
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
NombresSe escriben siempre: al resolver un MPC, al detectar la ubicación y al clicar el mapaHecho
Aviso de versiónLa 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 informeHecho

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.

PiezaEntregaEstado
Una páginaVIEW_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 pidHecho
Ancho de la listaArrastrable y recordado (projects_list_width, 260–560); doble clic en el separador lo restablece y « la pliegaHecho
Filas densas54 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 cabeHecho
Barra del cielogui/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 vistasHecho
Panel en reposogui/widgets/night_panel.py: la noche (mismo cielo pintado que Bienvenida) y el camino a un proyecto nuevoHecho
Una sola fuentecore/night_brief.py: los números y las frases de la noche, compartidos por las tres superficiesHecho
EncabezadoCampañas pasa a botón del encabezado; los ~105 px de las dos franjas vuelven a la listaHecho

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.

PiezaEntregaEstado
CromoLos 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álisisVisitas y curva una al lado de la otra (maestro-detalle), con la lista ajustada a su contenido en vez de una caja fijaHecho
GráficasChartView.sizeHint() (640x260) en vez del tamaño de la escena, y la geometría de la leyenda de la curva derivada de su propia fuenteHecho
FichaEl bloque de parámetros lleva su "A fondo" en la cabecera, la tabla mide lo que miden sus filas y los chips dejan de estirarseHecho
Criteriotests/unit/test_project_tabs_layout.py: ninguna de las dos pestañas pide más que el viewport a 1360x940 y 1360x860Hecho

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.

PiezaEntregaEstado
La bandagui/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 lecturasHecho
FichaDos 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 defectoHecho
CapturaUn panel "Telescopio y cámara" en vez de tres cajas, el estado en una fila y el plan dibujado sobre la noche con su veredictoHecho
AnálisisLas tomas de la visita dibujadas en la noche en que se hicieron, encima de las visitasHecho
ColorEl chip de conexión (verde/ámbar) y los valores de estado tintados por lo que significanHecho
CriterioLas cuatro pestañas caben en el viewport a 1360x940 y 1360x860, Captura incluida con CCDciel conectadoHecho

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.

PiezaEntregaEstado
La filaPará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 debajoHecho
El colapsoLas columnas son widgets y siguen a su bloque: sin gráficos los parámetros ocupan todo el ancho, y al revésHecho
El apiladoPor debajo de 660 px de panel la fila se vuelve vertical (los gráficos tienen 300 px de mínimo)Hecho
El 50/50Las etiquetas de las SectionCard envuelven: el mínimo del grid baja de 448 a ~180 px, que es lo que el reparto igual necesitabaHecho
El aterrizajeAbrir 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 suyoHecho

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.

PiezaEntregaEstado
La Lunagui/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ónHecho
Los crecientesLa 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 igualaHecho
El observatorioEstaba 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 ranuraHecho

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.)

PiezaEntregaEstado
La galaxiaLa 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,8Hecho
El iconoappicon.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íoHecho
La invitaciónEl 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 debajoHecho

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».

PiezaEntregaEstado
El paso de equipoBienvenida 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 appHecho
La escala contestaLa 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 binningplate_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 factorHecho
La limpiezaFuera 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 menosHecho
El asistente modalFuera 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.pyHecho

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.

PiezaEntregaEstado
El generadorwebsite/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 idempotenteHecho
La landingUna 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ítulosHecho
La guíaUna página por capítulo y idioma, con barra lateral, anterior/siguiente, anclas por encabezado y las cajas «Why …?» como calloutsHecho
El guardiántests/unit/test_site_build.py: el conversor, la regeneración byte a byte, los enlaces internos y que la paleta salga del temaHecho
En el programaAyuda → Guía de usuario (web), en el navegador del sistema; el visor interno sigue siendo la puerta sin redHecho
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.