# Reentrancia de Simplot

## Contrato de la API

`SimPlot::Contexto` permite mantener varios trabajos en el mismo proceso Perl. Cada contexto fija la base de las entradas, el destino y los ejecutables. Cada llamada de dibujo crea un subdirectorio de salida exclusivo y usa variables locales; no guarda un «ultimo modelo» o un «ultimo resultado» compartidos. El modelo y los argumentos del llamador no se modifican. Las excepciones no cierran el programa que usa la biblioteca.

```perl
use SimPlot::Contexto;

my $a = SimPlot::Contexto->new(base => '/datos/a', directorio => '/salidas/a');
my $b = SimPlot::Contexto->new(base => '/datos/b', directorio => '/salidas/b');
my $ra = $a->ejecutar('graficos.dat');
my $rb = $b->ejecutar('otro.dat');
my $ra2 = $a->ejecutar('graficos.dat'); # no reutiliza estado ni pisa ra

die $ra->{errores} if $ra->{estado};
print join("\n", @{$ra->{archivos}}), "\n";
```

| Operacion | Resultado |
|---|---|
| `new(base=>..., directorio=>...)` | Contexto independiente; rutas relativas fijadas respecto de la base al crearlo |
| `leer($archivo)` | DAT nuevo por llamada; acepta DAT o entrada del lector slex |
| `dibujar($modelo, $documento, ventanas=>0)` | Dibujo v2; devuelve `directorio`, `guion` y `archivos` |
| `ejecutar($archivo)` | Selecciona v2 para su DAT/DS, o el motor historico aislado para los otros casos |
| `ejecutar_historico($archivo, opciones=>[...])` | Compatibilidad v1 en otro proceso, sin ventanas ni terminal interactiva |

`ejecutar` devuelve ademas `motor`, `estado` (estado POSIX de espera), `codigo`, `senal`, `salida` y `errores`. Comprobar `estado`, no solo `codigo`: un proceso terminado por una señal puede tener codigo cero. Los errores de entrada y del motor v2 lanzan excepciones (`eval`); la salida fallida del proceso historico se conserva como resultado no exitoso. Se conservan los archivos de una corrida fallida para diagnostico.

## Configuracion y limites

- `lector` admite el ejecutable o una lista `[ejecutable, argumentos...]`; `interpola`, `gnuplot` y `simplot` permiten fijar rutas de herramientas. La nueva frontera de procesos usa listas de argumentos, no cadenas de shell. El motor historico conserva internamente sus invocaciones antiguas: el aislamiento no es un sandbox para archivos no confiables.
- `gnuplot` personalizado corresponde al motor v2. El motor historico conserva su busqueda de Gnuplot en PATH y rechaza un contexto con otro ejecutable, en vez de ignorarlo silenciosamente. La compatibilidad historica transmite la lista completa del lector mediante `SIMPLOT_LECTOR_JSON`, solo en el entorno del hijo.
- La API funcional `SimPlot::Dato::leer` conserva los diagnosticos por STDERR. `Contexto->leer` los incorpora a la excepcion sin imprimirlos; el llamador decide como registrarlos.
- Los valores predeterminados de `SIMPLOT_D2DATDINC` y `SIMPLOT_INTERPOLA` se toman al crear el contexto. El `PATH` y la zona horaria local siguen siendo los del proceso; usar rutas absolutas para fijar versiones de ejecutables y `zona: UTC` en el modelo para fijar fechas independientemente de la zona local.
- No modificar manualmente el hash interno del contexto mientras se usa. No se promete seguridad en manejadores de señales ni concurrencia entre hilos Perl que compartan manualmente estructuras mutables.
- La API historica admite solo `-noclas`, `-ncgf`, `-nokill`, `-noverb`, `-graf=4` y `-sinventana`. No expone limpieza, salida arbitraria ni preguntas interactivas. La interfaz de terminal tradicional conserva sus opciones.

## Que se volvio reentrante y que no

| Parte | Implementacion |
|---|---|
| Contexto, lectura DAT, modelo, resultados y dibujo v2 | Datos locales o propios del contexto; modelos clonados antes de ampliar/validar |
| Procesos slex, Tabular2 y Gnuplot | `SimPlot::Proceso`: captura con archivos privados, `fork/exec`, espera del PID propio; cambia directorio y descriptores solo en el hijo |
| Pares | Trabaja sobre una copia de los argumentos; no consume el vector recibido |
| ParaPlot/PlotGNU/AyudasPP historicos | **No se convirtieron internamente en modulos reentrantes**: se ejecutan por llamada en un proceso nuevo, con salida privada |

No llamar directamente a las rutinas globales historicas para alternar dos trabajos. Usar el contexto: el aislamiento de procesos es la frontera de compatibilidad, no una afirmacion de que desaparecieron sus globales. La ejecucion directa de `scripts/simplot.pl` sigue siendo un programa de terminal, no una funcion para cargar con `do` en otro programa.

## Archivos y responsabilidades

| Archivo | Cambio y propiedad del estado |
|---|---|
| `lib/SimPlot/Contexto.pm` | API publica; guarda solamente configuracion de la instancia, sin resultados acumulados |
| `lib/SimPlot/Proceso.pm` | Un PID y dos archivos de captura por llamada; restaura el estado de espera del padre y no redirige sus descriptores |
| `lib/SimPlot/Dato.pm` | Lector configurable por llamada; captura diagnosticos; ruta del lector del arbol fijada al cargar el modulo |
| `lib/SimPlot/Render.pm` | Herramientas por llamada, tablas y guion locales, destino exclusivo; ruta de Tabular2 independiente de cambios posteriores de cwd |
| `lib/SimPlot/Modelo.pm` | Elimina el objeto JSON mutable compartido; conserva clonacion y validacion del modelo |
| `lib/SimPlot/Pares.pm` | Copia el vector de argumentos antes de Getopt |
| `scripts/simplot.pl` | Conserva la terminal compatible; Contexto la invoca en un proceso nuevo para v1 |
| `scripts/instalar.pl` | No necesitaba cambios: instala automaticamente los nuevos modulos bajo `share/perl5` y esta documentacion |

Las entradas se resuelven respecto de `base`; las fuentes de un modelo se resuelven respecto del documento que lo describe, no respecto del destino. El padre conserva su directorio, entorno y `ARGV`. No se crea una dependencia nueva fuera del arbol: se usan Perl y las herramientas ya requeridas (slex, Tabular2, Gnuplot).

Las salidas `simplot-v2-*` y `simplot-legacy-*` son persistentes para que el llamador pueda consumirlas; no hay limpieza global ni destructor que borre resultados. El llamador decide cuando retirar el directorio exacto devuelto. Esto tambien vale para archivos parciales tras un error.

## Continuacion de la migracion interna

1. Trasladar opciones, tablas de unidades, seleccion de archivos y registros de ParaPlot/PlotGNU a estructuras por contexto.
2. Sustituir `exit`, manejadores globales y cambios de directorio por retornos, excepciones y rutas explicitas.
3. Probar cada lenguaje historico con A–B–A, llamada anidada y error intermedio antes de quitar su aislamiento.

| Modulo historico | Estado a trasladar en esa migracion |
|---|---|
| ParaPlot | Tablas de unidades, nombres y referencias al circuito; lexicos de traductor |
| PlotGNU | Opciones, selecciones, rangos, grupos, nombres de archivos y guiones; cambios temporales de directorio |
| AyudasPP | Opciones CLI, mensajes, registros, manejadores de archivos y decisiones de salida |
| GlobalesSP / AuxiliaresPP | Referencias y configuracion compartidas; separar constantes de datos de cada trabajo |

No se debe sustituir el aislamiento por un simple borrado de hashes al finalizar: una llamada anidada perderia el estado de la exterior. La condicion de cierre de la migracion sera ejecutar el corpus historico desde contextos en el mismo proceso sin globales mutables, `exit` ni cambios de cwd, manteniendo sus resultados.

El aislamiento permite usar ahora el codigo historico sin mezclar sesiones, mientras esa migracion se hace sin perder compatibilidad.

## Verificacion

Resultado final del 09/10/2026: `make check` correcto, **471 comprobaciones en 18 archivos**, incluidas 44 de reentrancia y 32 de instalacion. Se probaron graficos reales, instalacion temporal y compatibilidad de los lenguajes anteriores. No se hizo una instalacion del sistema ni se generaron paquetes nuevos con estos cambios.

`tests/reentrancia.t` ejercita A–B–A, una llamada de dibujo anidada en otra, dos dibujos concurrentes bajo el mismo destino, modelos originales intactos, error intermedio y recuperacion, herramientas diferentes por contexto, lectura con configuracion propia, motor historico aislado y preservacion del directorio, entorno y argumentos del llamador. `tests/install.t` comprueba que la API se incluye en la instalacion y ejecuta el motor historico desde un prefijo temporal instalado. La suite completa sigue cubriendo todos los lenguajes historicos y v2.

La primera corrida tras sustituir la captura de procesos detecto 13 incompatibilidades en los tests que esperaban los mensajes de slex por STDERR. Se preservo ese contrato en la API funcional y se separo del modo silencioso de Contexto; no se descartaron los tests ni los diagnosticos. Los resultados finales se registran en el changelog despues de ejecutar la suite.

Para reproducir desde la raiz de Simplot:

```sh
make check
prove -Ilib -v tests/reentrancia.t tests/install.t
```

Se agregan casos para DS v1 con configuracion completa del lector y reutilizacion del contexto despues de cambiar el directorio del llamador. Las pruebas generan graficos reales en directorios temporales; no instalan en el sistema ni publican la web. Desde fuentes, usar `perl -Ilib programa.pl`; una instalacion en prefijo propio requiere agregar su `share/perl5` al camino de Perl.
