El nodo de planta y las máquinas
Léase esto primero
- Las máquinas están previstas hasta que una quede conectada. El carril de máquinas está construido y contratado, y solo software lo ha manejado alguna vez.
- No se ha leído el manual de referencia de NCR para estas máquinas. Todo valor de protocolo que lleva el andamiaje es la lectura que hace el Laboratorio de descripciones de NDC que circulan públicamente, registrada como los supuestos A-46 a A-48 y sustituible máquina por máquina.
- La dirección del enlace es un marcador de posición. Hoy nada escucha en ella, y no hay ningún escucha desplegado en ninguna parte.
Qué es esto. El Laboratorio tiene cinco máquinas NCR Atleos en su planta. Este documento
dice cómo se integran a la Simulación del Ecosistema Financiero Virtual, qué sigue sin
conocerse sobre ellas y en qué orden va el trabajo.
Qué no es esto. No es una descripción de cómo funciona una NCR Atleos. **El manual de
referencia de NCR para estas máquinas no ha sido leído**, no se consultó ningún documento de
NCR, y cada valor de protocolo que carga el andamiaje es la lectura que hace el Laboratorio de
descripciones de NDC que circulan públicamente - registrada como los supuestos A-46,
A-47 y A-48 en [../spec/assumptions.md](../spec/assumptions.md) y sobrescribible,
máquina por máquina, desde nodes/floor/<machine>.yaml. Nada de lo aquí escrito debe tomarse
como una descripción de lo que las máquinas de la planta realmente hacen hasta que el
inventario de más abajo esté hecho.
Por qué existe un nodo de planta
Un terminal de autoservicio NCR real no decide nada. Sus tablas de estado dicen *«en este
estado, envía una solicitud de transacción y espera»*, y lo que ocurre después - cuál
pantalla, cuál acción de dispositivo, cuánto sale de los casetes - lo decide un host que
es dueño de esas pantallas y responde por NDC (NCR Direct Connect), sobre un enlace TCP.
El ecosistema simulado vive en Cloud Run, y Cloud Run no puede aceptar TCP crudo. A un
servicio de Cloud Run se llega por HTTP(S) a través del front end de Google, en el único
puerto que el contenedor declara; no hay un segundo escucha al que una máquina pueda abrirle
un socket, y nada en la configuración del servicio puede publicar uno. El mismo párrafo ya
está en [../deploy/README.md](../deploy/README.md) a propósito del escucha ISO 8583, por la
misma razón.
Así que el Laboratorio corre la misma imagen dos veces:
| Dónde | Qué es | |
|---|---|---|
| La pasarela en la nube | Cloud Run, ecosystem-gateway | HTTP, la consola, todos los carriles, el mundo de cada equipo. Sin escucha TCP, y no se configura ninguno. |
| El nodo de planta | una PC pequeña en la LAN del Laboratorio, en el segmento propio de las máquinas | FLOOR_NODE=1. Sostiene los puertos TCP de NDC e ISO 8583 que las máquinas marcan, y reenvía lo que piden al carril de máquinas de la nube con su propia llave de equipo. |
El nodo de planta es un participante del mundo en la nube, no un segundo mundo. No lleva
libro, no liquida nada, y no guarda ningún estado que un reinicio perdería salvo el diario del
día que todavía no ha entregado. Todo lo que una máquina pide lo responde el switch simulado
en la nube, sobre /v1/atm/**, exactamente igual que se le respondería a una integración que
llamara esos endpoints desde una laptop. El nodo de planta agrega un protocolo, no una
capacidad.
La lista de investigación
Nada de lo de abajo se sabe todavía. Cada línea es una pregunta con un responsable (Carlos), y
cada una bloquea algo concreto - por eso la lista es una tabla y no un deseo.
| # | Por averiguar | Qué bloquea |
|---|---|---|
| 1 | Modelo exacto de cada una de las cinco máquinas, y la versión de software que tiene. Conocido (2026-09-06): la insignia es una NCR Atleos SelfServ 62 (recicladora de efectivo de bolsillo para lobby interior sobre el Scalable Recycler, pantalla táctil de 15 pulgadas, panel superior de 18.5 pulgadas) corriendo NCR Atleos NDC Enterprise sobre Windows 11; NCR presenta NDC Enterprise como el sucesor multimarca de Advance NDC. Sigue pendiente: los otros cuatro modelos, y la versión de NDC Enterprise en cada uno | todo; no se puede escribir un perfil para una máquina que nadie ha identificado |
| 2 | Versión de NDC que habla el software, y la versión de XFS por debajo | todo el dialecto: entramado, clases de mensaje, orden de campos (A-46, A-47) |
| 3 | El manual de referencia de ese modelo y esa versión | reemplaza cada valor supuesto en packages/ndc-host/src/dialect.ts por uno leído |
| 4 | Acceso de supervisor - el modo, la credencial y quién la tiene | leer la configuración, el diario y los contadores de la máquina, del todo |
| 5 | Estado de llaves del EPP - llaves cargadas, puestas en cero, o nunca cargadas; y si el Laboratorio puede cargar las suyas | chip y PIN, paso cuatro. Nada anterior a eso necesita una llave, que es la razón por la que nada anterior espera |
| 6 | Tarjetas: qué existe, y si el Laboratorio puede grabar o codificar las suyas en el rango de BIN del Laboratorio que empieza en 9 | los flujos con tarjeta presente. El prefijo de PAN del Laboratorio está deliberadamente fuera de los rangos asignados |
| 7 | Llaves de CA para chip - cuáles llaves públicas de autoridad de certificación tienen las máquinas, y si se pueden cargar llaves de prueba del Laboratorio | EMV, paso cuatro |
| 8 | Modo de prueba de efectivo - si existe en estas máquinas y cómo se entra | dispensar sin mover billetes reales, paso tres |
| 9 | El diario electrónico - su formato, dónde se guarda, y cómo se lee desde la máquina | reemplaza la línea de diario del Laboratorio (A-48) por la propia de la máquina |
| 11 | Quién es dueño de las pantallas y del flujo. La página de NCR dice que NDC Enterprise le permite a un operador «gestionar el flujo de transacción y la interfaz de usuario a través del servidor empresarial». Si ese servidor está instalado en las máquinas del Laboratorio, y si el host (el nodo de planta) sigue descargando tablas de estado y pantallas a la manera clásica de NDC o lo hace el servidor empresarial, decide cuánto del papel de datos de personalización del host nos toca jugar | los mensajes de datos de personalización del host (A-46); posiblemente todo el reparto entre el host y el servidor empresarial |
| 10 | La VLAN - en cuál segmento están las máquinas, qué más lo alcanza, y si el nodo de planta puede ser la única ruta de salida | el límite de seguridad de todo el arreglo. Ver abajo |
La VLAN es el límite de seguridad, y eso es una decisión
No hay credencial en el enlace NDC, y eso se declara en vez de omitirse. Un terminal NDC
no tiene campo donde llevar una llave de equipo ni noción de presentar una, así que un escucha
que la exigiera sería un escucha con el que ninguna máquina puede hablar.
Lo que protege el enlace es la red en la que está. Las máquinas y el nodo de planta están en
una VLAN propia; el nodo de planta es lo único en esa VLAN que puede alcanzar internet; y
la llave de equipo vive en el nodo de planta, la presenta el nodo de planta al carril en
la nube, y nunca viaja por el enlace de las máquinas en ninguna dirección. Cualquier cosa que
alcance el puerto NDC puede manejar las pantallas de una máquina, así que la respuesta a
«quién puede alcanzar el puerto» es «las máquinas, y nada más».
En cualquier enlace que salga de la planta, configure NDC_TLS_CERT y NDC_TLS_KEY (y el par
de ISO 8583 junto a ellos). El TCP plano es el valor por defecto porque el hardware de planta
a menudo no puede hacer más y un escucha que rechaza la máquina no es un escucha.
Activos que el Laboratorio ya tiene
Existen renders y animaciones 3D de la SelfServ 62, un modelo de ella en Blender, y
fotografías realistas, y pueden proveerse a solicitud (Carlos, 2026-09-06). No están en este
repositorio. Sirven a las películas, a la página pública de la simulación y a un ensayo de la
disposición física de la planta; no hacen falta para el host, que solo llega a ver los
mensajes de la máquina.
Los cuatro pasos
El cable y NDC van en el mismo nodo de planta. Una caja, una llave de equipo, dos puertos:
NDC_TCP_PORT para las máquinas que hablan NDC e ISO8583_TCP_PORT para una máquina o un POS
en la misma planta que hable ISO 8583 en su lugar. Eso no es una comodidad, es el arreglo -
ambos escuchas necesitan TCP crudo, Cloud Run no lleva ninguno, y una segunda caja significaría
una segunda llave de equipo, un segundo diario por conciliar y dos lugares donde equivocarse
con la VLAN. La mitad de ISO 8583 tiene su propio perfil
([../spec/iso8583-wire.md](../spec/iso8583-wire.md)), su propio sign-on y su propio paquete
de conformidad (npm run wire:conformance); todo lo de abajo sobre la VLAN, el archivo de la
llave y las recetas de servicio le aplica palabra por palabra. Ese mismo emparejamiento es lo
que levanta la receta de Compute Engine en
[../deploy/README.md](../deploy/README.md) cuando el enlace tiene que salir de la planta.
1. Inventario. Trabaje la lista de arriba, máquina por máquina, y escriba lo encontrado en
nodes/floor/<machine>.yaml - el bloque de dialecto para lo que dice el manual, la lista
pending para lo que sigue abierto. En este paso ninguna máquina se pone en la red.
2. Nodo de planta y host, manejados por el ATM virtual y el switch virtual. *(Esto es lo
que existe hoy.)* El nodo de planta corre, ambos escuchas están arriba, y a cada uno lo
ejercita un doble de prueba con forma de cliente que marca un socket real y envía tramas
reales: NDC por packages/ndc-host/test/virtual-atm.ts - que ejecuta el function id que se le
da e informa de vuelta - e ISO 8583 por packages/iso8583-wire/test/virtual-switch.ts, que
hace sign-on, autoriza, reversa, retira con tarjeta y sin ella, y le responde el mismo mundo
del que responden los carriles REST. Ambos quedan probados antes de que exista una máquina,
que es la única manera de llegar al paso tres con algo que valga la pena conectar.
3. La primera transacción sin efectivo, en UNA máquina. Una máquina, en la VLAN, marcando
al nodo de planta. Solo consulta de saldo: lee una cuenta, imprime una cifra, no mueve
nada y no dispensa nada. Es la transacción más segura que existe y lo correcto para correr de
primero en una máquina cuyo acceso de supervisor y cuyo diario apenas se acaban de entender.
Lo que aquí se prueba es el enlace y el dialecto, no el ecosistema - el ecosistema lleva
meses respondiendo estas operaciones sobre REST.
4a. Retiro sin tarjeta con billetes de prueba. El código de un solo uso que se le dio al
teléfono del titular se teclea en la máquina, el switch gasta la orden exactamente una vez, y
los billetes salen - en modo de prueba de efectivo si las máquinas lo tienen, y con
billetes de prueba si no lo tienen. Sin tarjeta, sin PIN, sin EMV, que es lo que lo hace
ejecutable antes de que se cargue una sola llave. El diario electrónico se lee de la máquina
al final de la corrida y se concilia por POST /v1/atm/journal.
4b. Chip y PIN, después de la carga de llaves. Solo después de que los puntos 5, 6 y 7 de
la lista queden resueltos. Necesita un EPP con llaves, tarjetas en el rango de BIN del
Laboratorio, llaves de CA que las máquinas acepten, y un dialecto que haya sido leído en
vez de supuesto. Nada en el andamiaje calcula hoy un bloque de PIN, guarda una llave o
verifica un certificado, y nada debería hacerlo hasta este paso.
Cómo se corre un nodo de planta
El nodo de planta es la imagen ordinaria con entorno de nodo de planta. No necesita compilación
propia ni una rama de código.
docker run -d --name finlab-floor \
--restart unless-stopped \
-p 8583:8583 -p 9100:9100 -p 8787:8787 \
-e FLOOR_NODE=1 \
-e NDC_TCP_PORT=9100 \
-e ISO8583_TCP_PORT=8583 \
-e CLOUD_GATEWAY_URL=https://ecosystem.financial \
-e FLOOR_TEAM_KEY_FILE=/run/secrets/floor-team-key \
-v /etc/finlab/floor-team-key:/run/secrets/floor-team-key:ro \
-v /etc/finlab/nodes/floor:/app/nodes/floor:ro \
ecosystem-gateway:latest
El entorno
| Variable | Por defecto | Qué es |
|---|---|---|
FLOOR_NODE | vacío | 1 enciende el modo de nodo de planta. Vacío significa que este proceso no es un nodo de planta, que es lo que es cada instancia en la nube |
NDC_TCP_PORT | (requerido en modo planta) | el puerto que las máquinas marcan para NDC |
NDC_TCP_HOST | 0.0.0.0 | la interfaz a la que se enlaza el escucha |
CLOUD_GATEWAY_URL | (requerido en modo planta) | la pasarela en la nube a la que se reenvían las operaciones de máquina, p. ej. https://ecosystem.financial |
FLOOR_TEAM_KEY_FILE | vacío | un archivo que guarda la llave de equipo de la planta. Preferido: una variable de entorno la puede leer cualquier cosa que pueda listar el proceso, y un nodo de planta está donde alguien tiene acceso físico |
FLOOR_TEAM_KEY | vacío | la misma llave en el entorno, cuando un archivo es impráctico. Una de las dos es requerida |
FLOOR_NETWORK_ID | atm-network | el id del administrador de la red de ATM en el mundo en la nube, para la única operación a la que el carril de máquinas no le nombra una ruta |
FLOOR_PROFILES | nodes/floor | de dónde se leen los perfiles de máquina |
FLOOR_UNKNOWN_MACHINE | refuse | lo que recibe una máquina cuyo LUNO no está en ningún perfil. refuse cierra la conexión - el valor por defecto correcto en una planta. default habla el dialecto por defecto del Laboratorio, que es lo que quiere un banco de pruebas |
FLOOR_TIMEOUT_MS | 15000 | antes de que a una máquina se le diga que el host no pudo alcanzar su mundo |
NDC_MAX_CONNECTIONS | 16 | máquinas sostenidas a la vez; una de más se rechaza en el socket en vez de encolarse |
NDC_IDLE_TIMEOUT_SECONDS | 300 | silencio antes de que el escucha cierre una conexión |
NDC_TLS_CERT / NDC_TLS_KEY | vacío | rutas PEM. Ambas o ninguna: una sin la otra impide que el nodo de planta levante, en vez de arrancar calladamente un escucha en texto plano que alguien creyó cifrado |
ISO8583_TCP_PORT | vacío | el escucha ISO 8583, para una máquina o un POS en la misma planta que lo hable en su lugar. Lo lee la pasarela misma; ver [../deploy/README.md](../deploy/README.md) |
**Un nodo de planta arrancado sin llave, o sin una pasarela a la cual reenviar, se niega a
levantar.** Esa es la severidad correcta: una caja que acepta el socket de una máquina y luego
falla cada transacción es peor para la persona frente a la máquina que una caja que nunca
respondió.
Como servicio de systemd (PC de planta con Linux)
/etc/systemd/system/finlab-floor.service:
[Unit]
Description=FinLab floor node - NDC and ISO 8583 listeners for the Lab machines
After=network-online.target docker.service
Requires=docker.service
[Service]
Type=simple
Restart=always
RestartSec=5
# the key is a file the service user can read and nothing else can: 0400, owned by root
ExecStartPre=-/usr/bin/docker rm -f finlab-floor
ExecStart=/usr/bin/docker run --rm --name finlab-floor \
-p 9100:9100 -p 8583:8583 -p 8787:8787 \
-e FLOOR_NODE=1 -e NDC_TCP_PORT=9100 -e ISO8583_TCP_PORT=8583 \
-e CLOUD_GATEWAY_URL=https://ecosystem.financial \
-e FLOOR_TEAM_KEY_FILE=/run/secrets/floor-team-key \
-v /etc/finlab/floor-team-key:/run/secrets/floor-team-key:ro \
-v /etc/finlab/nodes/floor:/app/nodes/floor:ro \
ecosystem-gateway:latest
ExecStop=/usr/bin/docker stop finlab-floor
[Install]
WantedBy=multi-user.target
sudo install -m 0400 -o root -g root floor-team-key /etc/finlab/floor-team-key
sudo systemctl daemon-reload && sudo systemctl enable --now finlab-floor
journalctl -u finlab-floor -f
Como servicio de Windows (las máquinas son basadas en Windows, y la PC de planta puede serlo también)
Windows no trae un envoltorio nativo de «corre esto como servicio», así que use uno. Con
[NSSM](https://nssm.cc/) y Node 22 instalados, corriendo el repositorio directamente en vez del
contenedor:
# the key, readable by the service account and nobody else
icacls C:\finlab\floor-team-key /inheritance:r /grant:r "NT SERVICE\finlab-floor:(R)"
nssm install finlab-floor "C:\Program Files\nodejs\npm.cmd" "run gateway"
nssm set finlab-floor AppDirectory C:\finlab\finlab-ecosystem
nssm set finlab-floor AppEnvironmentExtra ^
FLOOR_NODE=1 ^
NDC_TCP_PORT=9100 ^
ISO8583_TCP_PORT=8583 ^
CLOUD_GATEWAY_URL=https://ecosystem.financial ^
FLOOR_TEAM_KEY_FILE=C:\finlab\floor-team-key
nssm set finlab-floor AppStdout C:\finlab\logs\floor.log
nssm set finlab-floor AppStderr C:\finlab\logs\floor.log
nssm set finlab-floor Start SERVICE_AUTO_START
nssm start finlab-floor
Abra los dos puertos hacia la VLAN de las máquinas y hacia nada más:
New-NetFirewallRule -DisplayName "FinLab NDC" -Direction Inbound -Protocol TCP `
-LocalPort 9100 -RemoteAddress 10.20.30.0/24 -Action Allow
New-NetFirewallRule -DisplayName "FinLab ISO 8583" -Direction Inbound -Protocol TCP `
-LocalPort 8583 -RemoteAddress 10.20.30.0/24 -Action Allow
Los dos flujos que el host responde hoy
Ambos están mapeados sobre operaciones que el switch simulado ya publica
(packages/atm-network/src/network.ts) y endpoints que el carril de máquinas ya lleva
(/v1/atm/**). El host no agrega ninguna capacidad propia.
Consulta de saldo
machine -> Transaction Request opcode buffer = the balance-inquiry code (A-47)
general-purpose buffer B = the account number
host -> /v1/atm/balance-inquiries { terminalId, issuerId, accountNumber }
host -> Transaction Reply function id = print only, screen = the balance screen,
the figure in the screen update and on the receipt
machine -> Solicited Status the reply was carried out
No se mueve nada y no sale nada. El function id es print only en cada camino a través de
este flujo.
Retiro sin tarjeta por código
(phone) -> /v1/atm/cardless/orders the holder's OWN institution mints the one-time code.
A machine never raises an order.
machine -> Transaction Request opcode buffer = the cardless-withdrawal code (A-47)
general-purpose buffer B = the one-time code
host -> /v1/atm/cardless/redemptions { terminalId, code }
host -> Transaction Reply approved: function id = dispense and print, and the
amount is THE ORDER'S, never the machine's
declined: function id = print only, the decline screen,
and the ISO 8583 DE039 on the receipt
machine -> Solicited Status the notes went out, or they did not
host -> a journal line, completed by that status
Se sostienen cuatro propiedades, y cada una está afirmada en
packages/ndc-host/test/flows.test.ts:
- Toda solicitud recibe una respuesta. Una máquina que recibe silencio no imprime nada, se le vence el tiempo, y le enseña a un cliente que las máquinas del Laboratorio se cuelgan.
- Un rechazo no dispensa nada. El function id se fija a partir del resultado, y no hay camino por el que una no-aprobación llegue a dispense and print.
- El monto es el del switch. Lo que el cliente tecleó en la máquina se registra y luego se ignora: la orden es la autoridad, y una máquina que pudiera nombrar su propio monto sería una máquina que podría pagarse a sí misma.
- Nada se dispensa dos veces. El switch gasta la orden antes de autorizar, y el host envía la misma respuesta ante un número de coordinación repetido en vez de iniciar una segunda transacción - que es lo que realmente necesita una máquina que perdió una respuesta y reenvió.
Conciliación del diario electrónico
Cada máquina lleva su propio registro del día. El switch lleva el suyo. Conciliar es encontrar
dónde discrepan, y la dirección interesante es la de la máquina: una línea de diario de la
que el switch no tiene registro es un pago que nadie va a liquidar, y tiene la forma de una
dispensación que ocurrió después de que se cayó el enlace. La otra dirección se encuentra de
todos modos, cuando se cuentan los casetes.
curl -sS https://ecosystem.financial/v1/atm/journal \
-H "x-lab-api-key: $FLOOR_TEAM_KEY" \
-H 'content-type: application/json' \
-d '{"journal":"2026-09-06T13:00:00.000Z|TERM-LAB-0001|cardless-withdrawal|ATM-atm-network-00001|200000|DOP|00|A"}'
El reporte dice qué cuadró (por la referencia, o - para una línea sin referencia - por el monto
y el terminal), en qué discrepa un par cuadrado, qué está solo en el diario, qué está solo en el
switch, y los dos totales con su diferencia.
El formato de línea es propio del Laboratorio, y es un marcador de posición (supuesto
A-48). El formato de los diarios de estas máquinas es el punto 9 de la lista. Cuando llegue, un
analizador para él produce los mismos objetos JournalLine y nada aguas abajo cambia - ni el
comparador, ni el reporte, ni el endpoint. Eso es lo que hace de esto un andamiaje y no una
adivinanza: la forma de la respuesta está resuelta y la lectura de los bytes no.
Dónde está el código
packages/ndc-host/
├── src/dialect.ts the dialect table - READ THIS FIRST. A-46, A-47, all profile-driven
├── src/messages.ts the six NDC message families as objects
├── src/codec.ts bytes to messages and back, and the TCP stream framer
├── src/profile.ts nodes/floor/<machine>.yaml, and the dialect overrides in it
├── src/lane.ts the three switch operations, over HTTP or in-process
├── src/host.ts the state machine: the two flows above
├── src/server.ts the TCP listener, one host per connected machine
├── src/floor.ts FLOOR_NODE and the environment
├── src/journal.ts the journal line and the matcher
└── test/virtual-atm.ts the machine that does not exist, on a socket that does
Cómo se conecta un switch socio, y el paquete de conformidad
Cómo se conecta un switch socio
La dirección es wire.ecosystem.financial:8583, y es un MARCADOR DE POSICIÓN. Se escribe
aquí para que una integración tenga un nombre que configurar y un solo lugar que cambiar cuando
de verdad se levante un enlace. **Hoy no hay nada escuchando en ella, y no hay ningún escucha
desplegado en ninguna parte.** Decir lo contrario sería la afirmación falsa más fácil de hacer
en este repositorio y la más cara de haber hecho.
Por qué todavía no hay a qué marcar, en simple:
El escucha necesita un host capaz de TCP, y la pasarela desplegada no lo es. La pasarela del ecosistema corre en Cloud Run, al que se llega por HTTP(S) a través del front end de Google, en el único puerto que el contenedor declara; no hay un segundo escucha al que un switch pueda abrirle un socket, ni configuración que publique uno. Así que el escucha corre donde el TCP crudo es posible, lo que hoy significa uno de exactamente dos lugares: el nodo de planta del Laboratorio - esta misma imagen en una PC pequeña en el segmento de red propio de las máquinas - o una VM pequeña de Compute Engine que el desplegador arranca, corriendo la misma imagen con
ISO8583_TCP_PORTconfigurado. Ambas son recetas que una persona sigue; ninguna está corriendo.docs/deploy/README.mdlleva la receta de la VM como comandosgcloudescritos por completo, y no han sido ejecutados.
Lo que hace un socio, en orden:
- Pedir una llave de equipo. El sign-on del cable toma una llave de equipo - el mismo valor que toman los carriles REST en
x-lab-api-key, deFINLAB_API_KEYS. Una identidad de servicio es una credencial HTTP y aquí no se acepta: su alcance se expresa en métodos y carriles HTTP, que un socket no tiene. - Decir desde cuáles direcciones marca el switch. Van en
ISO8583_ALLOWED_CIDRSen el escucha. Un par fuera de esa lista se destruye en el socket sin respuesta - sin negativa que leer, sin gastar un cupo de conexión, sin nada de qué aprender. En una VLAN de planta la lista puede dejarse vacía y la red es el límite; en cualquier enlace que salga de la planta no es opcional. - Tomar TLS.
ISO8583_TLS_CERTeISO8583_TLS_KEY, ambas o ninguna. Recuerde lo que va dentro del mensaje: DE052 lleva un PIN del Laboratorio en claro - la simulación no guarda llaves y no calcula ningún bloque de PIN, y §3 lo dice en la tabla - así que el transporte es lo único que lo protege. Sea simulado el PIN o no, un enlace que filtra uno enseña un mal hábito. El certificado de planta del Laboratorio es autofirmado; un socio debería fijarlo (pinning) en vez de apagar la verificación. - Correr el paquete de conformidad (§9) contra el escucha, desde la red propia del switch, y guardar el reporte.
- Hacer sign-on, y permanecer con sign-on. Una conexión, un mundo, una credencial presentada una vez. Se espera que el enlace sostenga el socket y haga eco (
0800DE070301) en vez de reconectar por transacción.
El entorno que lee el escucha
Cada una de estas la lee el proceso de pasarela que sostiene el puerto. Vacío significa que el
escucha no existe; eso es lo que usa cada instancia de Cloud Run.
| Variable | Por defecto | Qué hace |
|---|---|---|
ISO8583_TCP_PORT | vacío | el puerto. Vacío significa que no hay escucha en absoluto |
ISO8583_TCP_HOST | 0.0.0.0 | la interfaz a la que se enlaza |
ISO8583_ALLOWED_CIDRS | vacío | direcciones y bloques CIDR separados por coma o por salto de línea que pueden conectarse: 203.0.113.0/24, 10.20.0.5, 2001:db8::/32. Vacío significa que cualquier par puede conectarse. Un cliente IPv4 sobre un socket de doble pila (::ffff:203.0.113.7) se compara contra las reglas IPv4 como la dirección IPv4 que es. Una entrada malformada impide que el proceso levante, en vez de ser omitida |
ISO8583_TLS_CERT / ISO8583_TLS_KEY | vacío | rutas PEM. Ambas o ninguna: una sin la otra impide que la pasarela levante, en vez de arrancar calladamente un escucha en texto plano que alguien creyó cifrado |
ISO8583_MAX_CONNECTIONS | 32 | conexiones sostenidas a la vez; una de más se rechaza en el socket en vez de encolarse |
ISO8583_MAX_MESSAGES_PER_MINUTE | 600 | por conexión, sobre un cubo de fichas que se rellena continuamente. Pasado el límite, 96 con el reintento en DE126 — la conexión no se cierra |
ISO8583_IDLE_TIMEOUT_SECONDS | 180 | silencio antes de que el escucha cierre una conexión |
ISO8583_MAX_SIGN_ON_ATTEMPTS | 3 | llaves erróneas antes de que se cierre la conexión, para que el socket no sea un oráculo de llaves |
Cada mensaje está en la traza del mundo
Una conexión con sign-on escribe en la traza propia del equipo, bajo el actor iso8583-wire:
un event para el sign-on, luego un message y un reply por intercambio, cada uno cargando
el nombre de la conexión (iso8583-0007), el par, el equipo, y una **huella de ocho
hexadecimales de la llave** — suficiente para distinguir la credencial de un nodo de planta de
una llave de banco de pruebas seis semanas después, inútil como credencial en sí misma. La
representación es la misma que usa el log, así que **DE052 y DE127 se descartan antes de que se
escriba un carácter**. Eso es lo que hace distinguibles, después del hecho, un retiro por cable
y un retiro por REST, que es la pregunta que hace una conciliación.
El paquete de conformidad
La lista de lo que un switch tiene que hacer bien, y un ejecutor para ella:
npm run wire:conformance -- --host=127.0.0.1 --port=8583 --key=<team key> \
--gateway=http://127.0.0.1:8787
npm run wire:conformance -- --host=wire.example --port=8583 --key=… --tls --json
Marca al escucha con packages/iso8583-wire/test/virtual-switch.ts — el mismo switch de
tarjetas virtual que maneja la suite de pruebas, que es justo el punto: **el paquete no es una
descripción de las pruebas, es las pruebas, apuntadas a otra parte.** Sin --gateway, los
casos que necesitan material del Laboratorio (una tarjeta, un aceptante, un terminal, una orden
sin tarjeta) reportan SKIP con la razón y nunca se cuentan como aprobados; la disciplina del
enlace corre contra cualquier cosa. El código de salida es 0 cuando nada falló, 1 cuando
algo falló, y --json imprime el reporte completo.
**Un aprobado dice que el escucha habla este perfil. No dice nada sobre la certificación de
ninguna marca de tarjetas, y una corrida nunca sustituye a una.**
| Caso | Mensajes | Qué debe hacer un switch |
|---|---|---|
sign-on<br>hacer sign-on con la llave de equipo en DE127 | 0800 0810 | un 0800 con DE070 001 y la llave en DE127 se responde 0810 DE039 00, y la respuesta no lleva DE127: una credencial que regresa es una credencial en cada captura del enlace. |
sign-on-refused<br>una llave que el escucha no tiene se rechaza, y el socket no es un oráculo | 0800 0810 | una llave equivocada se responde 57 y la conexión se cierra tras el número configurado de intentos, de modo que el enlace no puede usarse para adivinar una. |
unsigned-refused<br>un mensaje financiero antes del sign-on se rechaza | 0200 0210 | cualquier mensaje que no sea el sign-on, en una conexión que no ha hecho sign-on, se responde 57 con DE126 indicando hacer sign-on primero. Nunca se ejecuta. |
credential-once<br>DE127 en cualquier mensaje que no sea el sign-on se rechaza de plano | 0800 0100 0200 | una credencial en un mensaje financiero se rechaza con 57 en vez de ignorarse: un terminal que presenta su llave en cada mensaje es un terminal cuya llave está en cada captura de paquetes. |
echo<br>prueba de eco | 0800 0810 | un 0800 con DE070 301 se responde 00 mientras el enlace esté arriba. Es con lo que un switch sondea y la manera más barata de distinguir un enlace muerto de uno callado. |
authorization<br>una compra en un aceptante | 0100 0110 | un 0100 que lleva DE002, DE042 y DE004 se responde 0110 con DE039, y en una aprobación con DE037 y DE038. El PAN regresa ENMASCARADO: la respuesta es la proyección, y la proyección lo enmascara.<br><em>necesita material del Laboratorio (una tarjeta, un aceptante, un terminal): pase --gateway.</em> |
authorization-declined<br>un rechazo es una respuesta | 0100 0110 | una autorización que el emisor no va a aprobar llega como un 0110 bien formado con su propio DE039 - nunca como una conexión caída. Una máquina que recibe silencio no imprime nada y reintenta.<br><em>necesita material del Laboratorio (una tarjeta, un aceptante, un terminal): pase --gateway.</em> |
reversal<br>la reversa de una autorización, por su propia referencia | 0400 0410 | un 0400 que lleva el DE037 con el que regresó el 0110 se responde 0410 00, y DE090 apunta al mensaje que se está reversando. Un 0400 sin DE037 se responde 30.<br><em>necesita material del Laboratorio (una tarjeta, un aceptante, un terminal): pase --gateway.</em> |
withdrawal-cardless<br>un retiro sin tarjeta en una máquina | 0200 0210 | un 0200 con DE003 010000, SIN DE002 y el código de un solo uso en DE102 se responde 0210. La orden se gasta exactamente una vez: un segundo canje del mismo código se rechaza.<br><em>necesita material del Laboratorio (una tarjeta, un aceptante, un terminal): pase --gateway.</em> |
withdrawal-card<br>un retiro con tarjeta y su PIN del Laboratorio | 0200 0210 | un 0200 con DE003 010000, DE002, DE041, DE100 y DE052 se responde 0210, y DE052 nunca se devuelve en eco y nunca se registra en log.<br><em>necesita material del Laboratorio (una tarjeta, un aceptante, un terminal): pase --gateway.</em> |
deposit<br>un depósito en una máquina | 0200 0210 | un 0200 con DE003 210000, el monto declarado en DE004 y DE002 o DE102 se responde 0210 con el resultado al que llegó el switch, reconocimiento incluido.<br><em>necesita material del Laboratorio (una tarjeta, un aceptante, un terminal): pase --gateway.</em> |
balance-inquiry<br>una consulta de saldo, que no mueve nada | 0200 0210 | un 0200 con DE003 310000, DE100 y DE102 se responde 0210 00 con la cifra en DE126. No contabiliza nada: DE054 no está en este perfil y no se registra ninguna transacción.<br><em>necesita material del Laboratorio (una tarjeta, un aceptante, un terminal): pase --gateway.</em> |
clearing<br>una autorización levantada por el cable compensa y liquida en neto | 0100 0110 | EL PERFIL NO LLEVA NINGÚN MENSAJE DE COMPENSACIÓN - ni 0320, ni 0500, ni 0600. Lo que debe sostenerse es que una transacción autorizada por el cable sea la misma transacción que el ciclo de compensación del adquirente recoge y liquida en neto, lo cual el Laboratorio prueba a través de POST /v1/cards/cycles y no a través de un MTI.<br><em>necesita material del Laboratorio (una tarjeta, un aceptante, un terminal): pase --gateway.</em> |
wire-rest-parity<br>el cable y el carril REST coinciden, en el resultado y en los asientos | 0100 0110 | la misma operación enviada por el cable y por el carril REST de tarjetas, en el mismo mundo, llega al mismo DE039 y mueve el mismo dinero. La respuesta ES la proyección REST, así que una diferencia aquí es una diferencia en el ecosistema y no en el enlace.<br><em>necesita material del Laboratorio (una tarjeta, un aceptante, un terminal): pase --gateway.</em> |
unknown-mti<br>una familia de mensajes que el perfil no lleva se rechaza, no se descarta | 0420 0500 | un MTI fuera de 0100/0200/0400/0800 - un aviso de reversa 0420, una reconciliación 0500 - se responde 12 con DE126 nombrando las cuatro familias. La conexión sobrevive. |
format-error<br>una trama que no es un mensaje se responde, y el enlace sobrevive | 0810 | bytes que no decodifican se responden 30 - en el MTI de respuesta propio de la solicitud cuando el MTI era legible, si no en un 0810 - y la conexión se mantiene arriba. |
oversized-frame<br>una trama que pasa el máximo del perfil termina la conexión | 0810 | un encabezado de longitud que declara más de 4096 bytes, o cero, se responde 30 una vez y la conexión se cierra: no hay delimitador en este protocolo sobre el cual resincronizar un flujo. |
rate-limit<br>una avalancha se frena, no se desconecta | 0800 0810 | pasado el presupuesto por conexión un mensaje se responde 96 con el reintento en DE126, y la conexión NO se cierra: un terminal que solo va rápido debe frenar, no caerse de la red.<br><em>un caso de límite: pase --include-slow.</em> |
sign-off<br>hacer sign-off, y el escucha cierra | 0800 0810 | un 0800 con DE070 002 se responde 00 y la conexión la cierra el escucha, de modo que un switch que terminó no retiene un cupo. |
_19 casos, 10 de los cuales corren contra cualquier escucha con nada más que un host, un puerto y una llave. Generado desde packages/iso8583-wire/src/conformance.ts por npm run spec:wire._
Deliberadamente **no hay mensaje de compensación en el paquete, porque no hay ninguno en el
perfil** (§2: ni 0320, ni 0500, ni 0600). La compensación y la liquidación aquí son un
ciclo que corre el adquirente, así que el caso clearing prueba que una transacción
autorizada por el cable es la que ese ciclo recoge y liquida en neto — a través de
POST /v1/cards/cycles, no a través de un MTI inventado. Por la misma razón un aviso de
reversa 0420 está en el paquete solo como algo que un switch debe rechazar con 12: la
reversa del perfil es 0400/0410.