# Acciones

> El editor de la acción: identidad y ubicación, el bloque de script, lo que un script puede leer y escribir, todos los mensajes de validación, el verbo Ejecutar y su registro, quién disparó la última ejecución, qué ejecuta la acción, Activar y Desactivar, Copiar y Eliminar, y los límites de una ejecución.

Una acción es un bloque de sentencias JavaScript que lee el espacio de direcciones y escribe variables de memoria y tags. Las acciones son el camino de escritura de Logic: se ejecutan a solicitud (Ejecutar), por un [disparador](logic-triggers), desde un [Botón](component-button) de panel de control conectado a ellas, o en el Arranque o la Finalización de una corrida de Process, por un [comando de acción de Logic](process-actions#logic-action-commands). Seleccione una acción bajo la sección Actions de la columna de Logic, o pulse **Abrir** en su fila de la tabla de su carpeta o de la raíz de su sección, para abrir su editor (una acción hecha con Nuevo… > Nueva acción no se abre, sino que queda resaltada en esa tabla, véase [Crear y copiar](logic#creating-and-copying)); leerlo necesita **Ver Logic**, editarlo **Configurar Logic**, y ejecutarlo **Operar Logic**.

El encabezado muestra la marca de acción, el nombre, la línea "Un script que usted ejecuta a solicitud, o que un disparador ejecuta por usted, y que escribe variables y tags." y una pastilla **Desactivado** mientras la acción está apagada. La columna del formulario lleva IDENTIDAD, SCRIPT DE LA ACCIÓN y **Usado por**; el riel lleva ÚLTIMA EJECUCIÓN.

## Identidad {#identity}

| Campo | Qué es | Valores / valor inicial | Efecto |
| --- | --- | --- | --- |
| Nombre | El nombre visible y el rótulo bajo el que se registra una ejecución. | Texto libre; una acción nueva es `ActionN`; un nombre en blanco se guarda como `Action`. | Se guarda solo con cada tecla. Las acciones se referencian por identidad, así que renombrarlas no reescribe nada ni rompe nada. |
| Línea de ubicación | Dónde está archivada la acción, en palabras: `Acciones / Arranque / Mover la consigna`. | De solo lectura. | Una acción no tiene dirección; la línea es un lugar, nunca algo que se escriba en una expresión. Cámbiela con **Mover a** en el árbol. |
| Descripción (opcional) | Texto libre. | Vacía al inicio. | Ninguno. |

## Script de la acción {#script}

La tarjeta SCRIPT DE LA ACCIÓN es el [editor de scripts](logic-expressions#the-script-editor) con la pista "Sentencias JavaScript (let, if, for, funciones). Escriba las variables de memoria por asignación (Variables.Setpoint = 21.5;) y los tags con Connector.Driver.Device.Tag.Write(value). Las lecturas no pasan por un filtro de calidad: verifique .State() y proteja los huecos con ?? antes de operar." El script se guarda solo con cada tecla y se valida cada medio segundo; el mensaje aparece debajo de la pista. La validación nunca ejecuta el script.

Un script es JavaScript ordinario en modo estricto (locales, `if`, bucles, funciones, `Math`, `Date`, `JSON`) sobre el mismo [espacio de direcciones](logic-address-space) y la misma [superficie de lectura](logic-expressions#the-read-surface-of-a-point) que las expresiones, con dos diferencias. Las lecturas **no** pasan por la compuerta de calidad: un hueco se lee como `null`, así que verifique `.State()` y proteja la aritmética con `??`; atender una lectura mala es justamente para lo que sirven las acciones. Y un script sí puede leer variables de expresión (su valor publicado); solo una expresión no las puede referenciar.

Un punto en vivo (un tag, una variable, una alarma, un canal de Process) es el punto, no su valor, así que lea su valor por un miembro: `if (Variables.Start.ValueToBool())`, `Variables.Level.ValueToReal() > 80`, `if (Alarms.High.IsActive)`. Escrito pelón en la prueba de `if`, `while`, `do`, `for` o `?:`, como operando de `!`, `&&` o `||`, en una comparación o en la aritmética (con `+=` y las demás asignaciones compuestas), siempre contaría como verdadero o nunca leería su valor: `if (Variables.Start) { … }` ejecutaría su bloque en cada ejecución, tenga la variable el valor que tenga. La validación lo rechaza y nombra el miembro que hay que leer en su lugar ([Lo que rechaza la validación](#what-validation-refuses)). Un punto pasado a una función, y cualquier miembro de un punto, queda como está escrito. Lo mismo vale para un `switch` sobre un punto pelón o un `case` que nombra uno (lea `.Value`), para el lado izquierdo de `??` (lea `.Value`) y para un marcador `${…}` de una plantilla literal (lea `.ValueToString()`). Una asignación compuesta a una variable (`Variables.Total += 1`, `-=`, `||=` y las demás) lee la variable como el punto antes de escribir, así que se rechaza con la asignación simple que funciona: `Variables.Total = Variables.Total.ValueToReal() + 1`.

### Lo que una acción puede escribir {#what-an-action-may-write}

Exactamente dos construcciones modifican la planta, y las dos pasan por el [punto único de escritura](logic#writing-values) que queda registrado:

| Escritura | Forma | Destino |
| --- | --- | --- |
| Asignación de memoria | `Variables.Setpoint = 21.5;` o `Variables.Horno.Setpoint = 21.5;` | Una variable de memoria, con los niveles de carpeta incluidos. El valor se fuerza al tipo de salida de la variable y se marca para retención cuando la variable es retentiva. Se aplica de inmediato, así que la línea siguiente del mismo script lee lo que acaba de escribir. |
| Escritura de tag | `Connector.Simulated.Pump.Cmd.Write(true);` o `…Cmd.WriteAndWait(true);` | Un tag en vivo. El valor es un valor de ingeniería: viaja hacia atrás por la [cadena de conversión](connector-value-pipeline) del tag hasta el valor bruto que toma el dispositivo, y el embudo lo entrega a la cola de entrega de ese dispositivo, donde los valores que se le deben salen de uno en uno y en el orden en que llegaron. |

Un tag lleva dos verbos de escritura, y el script elige según si necesita la respuesta:

| Verbo | Argumentos | Omitido | Devuelve | Espera al equipo |
| --- | --- | --- | --- | --- |
| `Write(value)` | El valor. | No hay nada que omitir. | Nada. | No. El valor se entrega a la cola del dispositivo y la línea siguiente corre. |
| `WriteAndWait(value, timeoutMs, continueOnFailure)` | El valor, cuántos milisegundos esperarlo, y si hay que seguir cuando falla. | `timeoutMs`: se usa en cambio el tiempo de espera del propio driver. `continueOnFailure`: `true`. | `true` solo cuando el equipo tomó el valor; `false` para cualquier otra cosa, incluido un valor rebasado. | Sí, hasta que llega el acuse o se agota el tiempo de espera. |

La espera ocurre en el carril de acciones, así que un dispositivo lento solo retiene a las acciones que van detrás de esta, y nunca retira la escritura: un valor que no se confirma a tiempo sigue viajando, y su acuse se registra cuando aterriza. Eso es lo que dice la respuesta, con todas sus letras: "'Cmd' no se confirmó en 2000 ms; el valor sigue de camino al equipo." Con `continueOnFailure` omitido o en `true` el verbo contesta `false` y el script continúa; con `false` la ejecución termina en esa línea, nombrando el destino y el motivo.

**Ninguna escritura aborta un script por sí sola.** Una escritura rechazada antes de que nada se envíe y una escritura que el equipo rechaza o nunca contesta fallan las dos a solas: el resto del script corre, y la ejecución termina como una ejecución correcta con escrituras fallidas. Tampoco se deshace nada, así que lo que se aplicó antes de una escritura fallida se queda aplicado, y el diario técnico muestra cada escritura como valor anterior a valor nuevo bajo la fuente Acción. Los motivos con los que se lee un rechazo:

- "Se rechazó la escritura en 'X': el valor no es finito." (NaN o infinito)
- "'Tag' no está materializado en el connector." (un dispositivo desactivado, o un tag que Connector no lleva)
- "No se pudo convertir 'value' para 'Tag'."
- "'X' está protegida contra escritura por '`<quien la retiene>`'." (una corrida de receta o un enclavamiento retienen la fuente)
- "No se escribió en 'Cmd': el equipo no está conectado." (el dispositivo ya se sabe caído, así que no se encola nada para él)
- "Falló la escritura en 'Tag': Connector no está en marcha."
- "No se encontró la variable 'X'." y "'Variables.X' es una variable de expresión: se calcula, nunca se escribe."
- Lo que haya contestado el equipo o su driver, con sus propias palabras, cuando el rechazo vino del cable.

Un valor que una escritura más nueva al mismo tag rebasó en la cola del dispositivo **no** es una falla: "'Cmd' se reemplazó por un valor más reciente antes de enviarse." es exactamente lo que pasó, al equipo se le dice el valor más nuevo, y no se informa en ninguna parte salvo a un `WriteAndWait`, que contesta `false` porque este valor no es el que salió.

Lo que sí termina una ejecución: un error del propio script (un error de sintaxis, una dirección que no nombra nada, una excepción), un `WriteAndWait(…, false)` cuya escritura falló, y los límites de ejecución de [Ejecutar](#run).

Qué fallas puede informar la propia ejecución se desprende de quién esperó. Un `Write` que se rechaza antes de que nada se envíe se rechaza mientras el script sigue corriendo, así que la ejecución lo cuenta y el registro de acciones lo dice; un `Write` que el equipo rechaza después se contesta mucho después de que la ejecución terminó, así que quien lo lleva es la consola de [Eventos](events) bajo el nombre de la acción, y nunca la ejecución. Un `WriteAndWait` se quedó por la respuesta, así que lo cuenta vaya como vaya esa respuesta.

## Lo que rechaza la validación {#what-validation-refuses}

| Mensaje | Causa |
| --- | --- |
| "Se necesita un script." | El editor está vacío. |
| "Error del script: …" | El texto no se puede interpretar. |
| "Las direcciones deben ser estáticas: se accede a 'Root' con un segmento calculado." | `Variables[nombre]` o cualquier segmento que no sea literal. |
| "Dirección incompleta '…'." | Una referencia demasiado corta para nombrar un punto. |
| "No se encontró 'X'." | Una lectura, o un destino de asignación, que no resuelve a nada. |
| "'X' es un valor: léalo sin .Value." | Un miembro de punto en vivo (`.Value`, `.ValueToBool()`, `.State()`, …) leído en un punto que entra al script como su valor: `System.StartTime.Value` o `Process.Oven_soak.Oven_1.Procedure.Running.ValueToBool()`. El mensaje nombra el miembro que se escribió. |
| "'X' es el punto en sí, no su valor: como condición siempre cuenta como verdadero. Lea su valor con X.ValueToBool()." | Un punto en vivo escrito pelón donde se prueba si es verdadero: la prueba de `if`, `while`, `do`, `for` o `?:`, o un operando de `!`, `&&` o `\|\|` (`if (Variables.Start)`). En una alarma el mensaje nombra `.IsActive`. |
| "'X' es el punto en sí, no su valor: una comparación o un cálculo nunca lee su valor. Lea su valor con X.ValueToReal()." | Un punto en vivo escrito pelón en una comparación o en la aritmética (`Variables.Level > 80`, `total += Variables.Level`). Para `==`, `!=`, `===` y `!==` el mensaje nombra `.Value`, y en una alarma `.IsActive`. Un `switch` sobre un punto pelón, o un `case` que nombra uno, recibe el mensaje con `.Value`. |
| "'X' es el punto en sí, no su valor: nunca está vacío, así que ?? siempre se queda con él. Lea su valor con X.Value." | Un punto en vivo escrito pelón a la izquierda de `??` (`Variables.Level ?? 0`). En una alarma el mensaje nombra `.IsActive`. |
| "'X' es el punto en sí, no su valor: escrito dentro de un texto nunca muestra su valor. Lea su valor con X.ValueToString()." | Un punto en vivo escrito pelón en un marcador `${…}` de una plantilla literal. En una alarma el mensaje nombra `.IsActive`. |
| "'+=' lee X como el punto en sí, no su valor, antes de escribir. Use X = X.ValueToReal() + 1." | Una asignación compuesta a una variable: `+=`, `-=`, `*=`, `/=`, `%=`, `**=`, las de bits, `&&=`, `\|\|=` y `??=`. El mensaje nombra el operador escrito y la asignación simple que hace lo que se lee, con el lado derecho tal como se escribió: `.ValueToReal()` para la aritmética, `.ValueToInteger()` para las de bits, `.ValueToBool()` para `&&=` y `\|\|=`, `.Value` para `??=`. |
| "Asigne a la propia variable: Variables.Name = value." | El lado izquierdo del `=` nombra un miembro, o baja más de lo que puede la dirección de una variable. |
| "'X' es una variable de expresión: se calcula, nunca se escribe." | Asignar a una variable de expresión. |
| "Los puntos de 'Connector' son de solo lectura. Escriba los tags con Connector.Driver.Device.Tag.Write(value)." | Asignar a cualquier raíz que no sea Variables (Process tiene su propio mensaje). |
| "Los puntos de Process son de solo lectura en las acciones; los comandos de corrida y las escrituras de preparación no se admiten desde scripts." | Asignar a un punto de Process, o llamar `Write` sobre él. |
| "Los comandos de corrida de Process no se pueden llamar desde las acciones; los puntos de comando exponen valores de disponibilidad de solo lectura." | Llamar `Process.<Modelo>.<Unidad>.Commands.Start()` o cualquier otro punto de comando; un punto `Commands` solo lee si el comando se podría ejecutar ahora. |
| "Los operadores ++ y -- no se admiten en los puntos. Use Variables.Name = Variables.Name.ValueToReal() + 1." | `Variables.N++`. |
| "Write(value) es la escritura del tag: Connector.Driver.Device.Tag.Write(value)." | `Write` llamado sobre algo que no es una dirección de tag. La frase nombra el verbo que se usó, así que `WriteAndWait` recibe la misma con su propio nombre en los dos lugares. |
| "El tiempo de espera de WriteAndWait tiene que ser un número de milisegundos: WriteAndWait(value, 2000)." | El segundo argumento está escrito y no es un número. |
| "El continuar si falla de WriteAndWait tiene que ser true o false: WriteAndWait(value, 2000, false)." | El tercer argumento está escrito y no es `true` ni `false`. |
| "WriteAndWait admite como máximo tres argumentos: el valor, el tiempo de espera en milisegundos y si continúa cuando falla." | Un cuarto argumento. |

Los tres mensajes de `WriteAndWait` juzgan lo que dice el propio texto. Un argumento que el editor no puede resolver leyéndolo (una variable, una expresión calculada) se juzga cuando la llamada de verdad se hace, y esa misma frase termina entonces la ejecución en esa línea.

Las variables locales de JavaScript (`let x = 1; x = x + 1;`) no las tocan estas reglas.

## Ejecutar {#run}

**Ejecutar** es el verbo inicial de la barra sobre una acción abierta; se dibuja solo para un rol con **Operar Logic** y queda en gris con "Runtime detenido" mientras la estación está en solo lectura (el bloqueo de ingeniería no lo pone en gris: ejecutar es operar, no configurar). Cuando el rol tiene además Configurar Logic, el borrador del editor se guarda primero y la ejecución corre la definición guardada; sin él, la ejecución corre la definición permanente (los campos son de solo lectura, así que las dos nunca difieren).

Cada ejecución se hace en el **carril de acciones** con un motor de scripts nuevo, así que no se filtra estado entre ejecuciones y dos scripts nunca corren a la vez: corren de uno en uno, en el orden en que se pidieron. El carril no es el ciclo de 100 ms. Las expresiones, las alarmas, los reconocimientos y las escrituras que llegan de otras partes siguen su marcha mientras un script corre, así que un ciclo puede caer en medio de uno y dos lecturas del mismo script pueden ver ciclos distintos; a cambio, una acción que se detiene a esperar a un dispositivo retrasa solo a las acciones encoladas detrás de ella y nada más en la estación.

Una ejecución se aborta pasados 500 ms ("El script se abortó: superó el límite de tiempo de 500 ms (¿bucle infinito?)."), 100,000 sentencias o 4 MB de memoria, cada uno con un mensaje que nombra el límite. Los 500 ms miden el script corriendo: la cuenta se detiene todo el tiempo que un `WriteAndWait` esté esperando a un dispositivo y se retoma después, así que a un script correcto nunca se le culpa de un equipo lento. Una acción desactivada falla sin ejecutarse ("La acción está desactivada."); un runtime detenido contesta "El runtime de la lógica no está en ejecución."

El registro de acciones informa "Ejecución correcta en 12 ms." cuando todas las escrituras llegaron, "Ejecución correcta en 12 ms, 1 escritura fallida: Cmd: No se escribió en 'Cmd': el equipo no está conectado." cuando el propio script terminó y una escritura no (con "Ejecución correcta en 12 ms, 3 escrituras fallidas, la primera: Cmd: No se escribió en 'Cmd': el equipo no está conectado." a partir de la segunda falla), o "Error en la ejecución: `<motivo>`" cuando la que acabó mal fue la ejecución misma.

A la consola de [Eventos](events) llega la ejecución fallida, bajo el nombre de la acción junto con lo que la invocó: Manual para el verbo, Dashboard para un Botón, MCP para el agente, el nombre del disparador para una ejecución programada, y `Process · <unit> · <action>` para una corrida de Process. Se registra como advertencia la primera vez, se resume mientras siga fallando por la misma causa, y se anota una vez más cuando se recupera, y esa línea de recuperación es la única entrada que una ejecución correcta escribe ahí. Una escritura rechazada llega a la consola del mismo modo y bajo el mismo nombre, y por el mismo motivo se nombra a la acción y no a la ejecución: la respuesta del equipo puede llegar segundos después de que la ejecución terminó, y ya no queda ejecución que hacer fallar. Una ejecución que simplemente sale bien, y todos los valores que escribe, se escriben solo en la [captura detallada](events-console#detailed-capture): un script que escribe en cada ciclo produce valores por millares, y una consola llena de ellos es una consola en la que nadie puede leer las novedades de la estación.

El riel ÚLTIMA EJECUCIÓN muestra cuándo se ejecutó la acción por última vez (o "Nunca se ha ejecutado."), "Duración: N ms", quién la disparó (abajo), el último error en una nota de precaución, las escrituras fallidas de esa ejecución en una nota de precaución propia (la misma frase que llevó el registro de acciones, así que una ejecución que salió bien y aun así dejó una consigna donde estaba también lo dice aquí), y el recordatorio "Las ejecuciones se hacen en el carril de acciones, de una en una. Solo una ejecución fallida llega a la consola de Eventos; una ejecución correcta, y todos los valores que escribe, se guardan para la captura detallada. Los disparadores invocan las acciones según su programación." El riel pertenece a la acción viva: un guardado automático nunca lo deja en blanco; un reinicio del runtime sí.

### Disparada por {#fired-by}

Debajo de la duración, la línea "Disparada por: …" dice qué ejecutó la acción por última vez:

| Qué la ejecutó | La línea dice |
| --- | --- |
| **Ejecutar** en esta página | "Disparada por: Ejecución en esta página" |
| Un Botón de panel de control | "Disparada por: Un botón del panel de control" |
| El agente (`action_run`) | "Disparada por: Agente" |
| Un disparador | El nombre del disparador: "Disparada por: Heater off when too hot". |
| Un comando de acción de Logic de una corrida de Process | La unidad y el grupo de acciones: "Disparada por: Process · Oven 1 · After soak". |

## Usado por {#used-by}

La tarjeta **Usado por** lista todo lo que en la estación ejecuta la acción ("Todo lo que en la estación ejecuta esta acción."), leído al abrir la acción: una fila por cada comando de Process que la dispara, por cada disparador vinculado a ella y por cada Botón de panel de control que la ejecuta.

| Tipo | Ubicación | Nombre |
| --- | --- | --- |
| **Comando de Process** | El modelo. | El grupo de acciones y el momento: `After soak · Finalización`. |
| **Disparador** | Vacío. | El disparador, bajo sus carpetas. |
| **Botón** | El panel de control. | El rótulo del Botón. |

Sin nada, dice "Nada en la estación ejecuta esta acción."; cuando las referencias no se pueden leer, "Puede que esta acción siga en uso. Inténtelo de nuevo antes de eliminarla o renombrarla." con **Intentar de nuevo**. La tarjeta solo lee, así que todo rol la ve, también mientras la estación está bloqueada en ingeniería. Léala antes de desactivar o eliminar una acción: un comando de Process cuya acción desapareció no ejecuta nada y lo dice en su propia página.

## Leer la corrida que la disparó {#reading-the-run-that-fired-it}

Una acción que una corrida de Process dispara en la Finalización lee esa corrida bajo la rama [LastRun](process-addresses#lastrun) de la unidad, que ya la tiene cuando la acción se ejecuta; una disparada en el Arranque lee los metadatos de la corrida que arranca bajo `Procedure.Metadata`. Los puntos de Process se leen como valores simples, sin `.Value` detrás, a diferencia de las variables, y un punto que la corrida no tiene se lee `null`, así que protéjalo:

```js
if (Process.Oven_soak.Oven_1.LastRun.Verdict === "Ok") {
  Variables.Last_serial = Process.Oven_soak.Oven_1.LastRun.Metadata.Serial;
}
const peak = Process.Oven_soak.Oven_1.LastRun.Evaluations.Soak_check.Temperature_in_range.MaxValue;
if (peak !== null) {
  Variables.Last_peak = peak;
}
```

Las direcciones se escriben completas, como toda dirección de un script: una dirección no se puede armar a partir de una variable.

## Los comandos {#commands}

| Comando | Qué hace | Gris cuando | No se dibuja cuando |
| --- | --- | --- | --- |
| Ejecutar | Guarda el borrador (con Configurar Logic) y encola una ejecución. | El runtime está detenido. | El rol no tiene Operar Logic. |
| Activar / Desactivar | Cambia Activado; una acción desactivada rechaza Ejecutar y deja inactivo a cada disparador vinculado a ella, y un comando de Process que la dispara se rechaza cada vez y queda en Revisión. | La estación está bloqueada o en solo lectura. | El rol no tiene Configurar Logic. |
| Copiar | Duplica la acción en la raíz Actions con un nombre libre y resalta la copia en la tabla de esa raíz en lugar de abrirla ([Crear y copiar](logic#creating-and-copying)); la copia no tiene ninguna ejecución registrada ni ningún disparador vinculado. | La estación está bloqueada o en solo lectura. | El rol no tiene Configurar Logic. |
| Eliminar la acción | Pregunta "¿Eliminar 'X'? Esto quita la acción. Esta acción no se puede deshacer." Un disparador vinculado a ella se queda sin vínculo, no se elimina; un comando de Process que la ejecuta se queda, no ejecuta nada y queda en Revisión. | La estación está bloqueada o en solo lectura. | El rol no tiene Configurar Logic. |

El panel de la raíz Actions lista todas las acciones con su estado en la columna Valor: "Activado · última ejecución `<cuándo>`", con "· error" cuando la última ejecución falló, "· escritura fallida" cuando salió bien y una escritura suya no, o "Desactivado".

## Lo que las acciones no hacen {#what-actions-do-not-do}

No lanzan comandos de corrida de Process, no preparan un procedimiento ni agregan un comentario a una corrida (esos puntos son valores de preparación y de disponibilidad de solo lectura); no reconocen alarmas; no corren en paralelo; no guardan estado entre ejecuciones ni comparten funciones; y no se lanzan por cambios de valor ni por condiciones, solo con Ejecutar, con el intervalo de un disparador, con un botón de panel de control, con el agente o con el comando de una corrida de Process. Una corrida de Process nunca espera a la acción que dispara, y el resultado de la acción nunca se escribe en la corrida.
