Carlos → Víctor · 10 de septiembre de 2026

La puerta de lectura

Los tres datos que pediste, y las tres correcciones que las mediciones me hicieron a mí.

Todo lo de acá está medido entre las 09:19 y las 09:41 -03 (12:19–12:41Z), contra la fuente real. Nada sale de acordarme: cada número tiene al lado el comando que lo sacó.

Lo que necesitás para ejecutar

  1. La llave pública

    Es la misma que ya tenés, no cambió.

    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINk7L4LT4QP4o/rZfc2X9nI4Ew1Ysug9iZYo0n2JZeTb carlos@mirofish

    Huella, para cotejarla sin depender de que el copiar/pegar no meta un salto de línea:

    256 SHA256:I2XrHy+TZ+TCq3c+Gioag9Skwk6NS+QsznqyB6Bn9l8 carlos@mirofish (ED25519)
  2. El usuario: carlos_ro

    No pido shell. Ya está en mi script de prueba, y te doy la línea textual porque el literal carlos_ro@147.15.33.223 no aparece armado en el archivo — son dos variables con esos valores por defecto (scripts/probar-la-puerta-de-lectura.sh, línea 8):

    HOST="${PUERTA_HOST:-147.15.33.223}"; USER_RO="${PUERTA_USER:-carlos_ro}"; LLAVE="${PUERTA_LLAVE:-$HOME/.ssh/carlos_mirofish}"

    Lo aclaro porque si lo buscabas por el literal no lo ibas a encontrar, y eso es media vuelta tuya perdida por una cita mía imprecisa.

  3. Qué alcanza cada puerto

    Dos objetos, no dos bases enteras.

    puertobaseobjetotamaño esperado
    15432wschatwoot_internalconversation_outcomes38.599 filas
    15433chatwootv_bi_chatwoot_mensajes1.400.164 mensajes

    Y lo que tiene que dar permission denied: contacts. Son los 36.854 teléfonos, y que no se lean es uno de los seis controles que corro yo — si contacts se lee, mi propio script se pone rojo, no verde.

El rol y las «dos credenciales»: en este diseño es una sola

Tenés razón en general, y en el diseño que quedó escrito en agosto no aplica: no es un túnel con psql de mi lado, es comando forzado sobre la llave. Mi script manda el SQL por stdin del ssh y lee la salida:

printf '%s\n' "$1" | ssh -o BatchMode=yes -i ~/.ssh/carlos_mirofish carlos_ro@147.15.33.223

Con eso el rol de postgres lo elegís vos del lado del servidor y yo nunca tengo una contraseña de base. Es una credencial, no dos, y la que no existe no se me puede filtrar.

Si preferís el bastión con permitopen, ahí sí son dos y no discuto. Mi script no cambia: toma PUERTA_HOST, PUERTA_USER y PUERTA_LLAVE por variable de entorno. Si te resulta más limpio un usuario por puerta (carlos_ro_etiquetas / carlos_ro_texto), también entra sin tocar código. Las dos me sirven. La única que no me sirve es media puerta.

Media puerta no es verde

Iba a devolverte el 15433 por prudencia —vos mismo marcaste que por ahí pasa contenido de conversaciones con clientes— y fui a medir si me alcanzaba con las etiquetas. No me alcanza, y el que me corrigió fue mi propio script:

la ETIQUETA vive en 15432 · wschatwoot_internal.conversation_outcomes    38.599 filas
el TEXTO    vive en 15433 · chatwoot.v_bi_chatwoot_mensajes           1.400.164 mensajes
MEDIA PUERTA NO ES VERDE. Si el 15433 no contesta → NO_PUDE_MEDIR, no verde.

La etiqueta es la respuesta y el texto es la pregunta. Evaluar el cerebro contra 38.599 casos sin el texto sería calificar un examen leyendo sólo la hoja de respuestas. Ese control lo agregaste vos el 31-ago, cuando cazaste que mi script tenía 0 menciones de v_bi_chatwoot_mensajes.

Devolvértelo habría sonado prudente y me habría costado otra vuelta tuya.

/ops/ · tenés razón, y lo cerré sin molestarte

Corrijo dos cosas mías: el «da 404 desde afuera» del 09-sep, y mi propio párrafo de hace un rato que decía que no podía medirlo. Sí podía. Y la razón por la que no lo encontraba es la que menos esperaba: el host no está en alarmas.com.py.

Está en core-qa.zymplo.com (136.248.102.79). Lo saqué de tu propio inventario —infra, servers/prx-prod-02/nginx/core-qa.zymplo.com.conf:120— y lo medí a las 09:34 -03:

ruta en core-qa.zymplo.comrespuesta
/ops/302 → /oauth2/start?rd=…/ops/ — exactamente como lo describiste
/health200 — control positivo: el host sirve, y ese location está fuera del SSO a propósito (línea 89)

Y tu cita del vhost es textual: la línea 121 dice «antes esto era return 404; lo que se publica ahora va DETRAS de Keycloak». Cuando yo canté el 404 era verdad. Lo que hice mal fue repetirlo el 09-sep sin re-medir, y encima sobre hosts que no eran el objeto.

Un matiz que te devuelvo, y que tu inventario ya tenía escrito

El 302 no prueba que /ops/ exista. Medido en la misma corrida, mismo host:

el mismo host, la misma corrida, dos rutas
ruta que existe 302 /ops/
rd=…/ops/
ruta inventada 302 /zz9-hermano-inventado-nunca-existio
rd=…/zz9-hermano-inventado-nunca-existio
Un catch-all location / con auth_request (línea 123): cualquier ruta —exista o no— sale con su 302 y su propio rd=.

Lo que prueba que /ops/ está publicado no es el código de estado: es tu vhost, la línea 120, que lo nombra.

la forma

El discriminador no es el redirect: es el hermano inventado en el mismo host. Un código de estado sólo significa algo cuando difiere del que da una ruta que no existe.

Tu regla es correcta y me la llevo. Pero el arreglo no es seguir el redirect: si sigo el 302 aterrizo en un login y concluyo «existe» — y una ruta inventada me diría lo mismo. Sería un verde falso sobre una fila que vos das por cerrada. Lo comprobé en dos hosts más:

hostruta real vs. inventadamiente…
bi.alarmas.com.pylas dos, el mismo 302 al mismo /auth/logindiciendo que no existe
wschatwoot.alarmas.com.pylas dos, 200 (catch-all de SPA)al revés: diciendo que existe

Y esto lo tenías fichado vos, antes que yo: infra/CLAUDE.md:668 dice «probe pendiente (302 del SSO ⇒ http_2xx lo leería como caída)». Tu inventario tenía la trampa escrita antes de que yo la pisara.

no es para vos · es para Carlos

El Observatorio del core —el motor que se declara neutral entre las dos casas— se publica hoy en un nombre zymplo.com y se autentica en el realm zymplo-prod, con los grupos /devops y /devs de esa casa.

No te lo cuento como novedad: yo ya lo tenía medido y anotado el 07-sep 21:0x en mi libro de pendientes (fila A177), con este host y con la nota de que el realm es zymplo-prod. Medido contra HEAD a las 09:41: aparece 0 veces en el CLAUDE.md de Alarmas y 2 en el libro.

Y eso hace peor mi error del 09-sep, no mejor: te escribí «el Observatorio da 404 desde afuera» contradiciendo una fila mía de dos días antes que ya decía este host y ya decía que el código de estado no discrimina. La respuesta no estaba en tu inventario esta vez: estaba en el mío.

Lo que NO tenés que hacer

Las 5 CICLO_*: no las cargues. Ya te lo había retirado el 09-sep y te lo re-mido hoy 09:24 -03, porque «mencionar» no es «leer»:

process.env / configService.get sobre CICLO_*  en core/src  →  0 coincidencias

Aparecen 3 veces en src/salud/el-inventario-de-la-casa.ts, y las tres están dentro de un bloque de comentario que explica que AlóChat las leía y ya no. El PR core #112 se llevó src/ciclo.ts y su módulo. Cargarlas sería trabajo tuyo para alimentar a un lector que no existe.

El postgres de core-qa sí sigue faltando y ése sí lo necesito — tu verificación coincide con la mía.

Los números que no coinciden, sin maquillar

Medido desde fb3396fe —tu punto de partida, el que efectivamente corre—:

vosyo¿coincide?
commits160160
archivos300384no
en src/205280no

Los commits dan igual, así que el punto de partida es el mismo y tu corrección es correcta: yo medía desde f6fbe131, que no es lo que corre. Probé cuatro definiciones y ninguna da 300 ni 205. El 280 es estable —da lo mismo desde los dos puntos de partida—, así que no depende de la base. No sé de dónde sale tu 205, y no lo necesito: ninguna de las cifras cambia la decisión, y el número que sí decide —los 160 commits— coincide exacto.

Las migraciones, para que el deploy no traiga sorpresa

ramamigración más nuevapendientes
origin/main (de donde despliega producción)20260907220000_suscripciones_de_push0
origin/develop20260910120000_donde_vive_la_conversacion_de_alo2 más

Después del deploy que autorizaste, código y base quedan a la par en main. El día que develop pase a main van a aparecer 2 migraciones nuevas. No es un problema: es para que no te sorprenda un pendientes: 2 en el próximo tren.

Y lo tuyo, que es lo que importa antes de desplegar: la base quedó adelante del código. Es seguro porque es puramente aditiva —crea tabla, dos índices y una FK sobre esa misma tabla, sin un DROP ni un UPDATE— pero el código que la usa todavía no está arriba.

Una sola forma explica mis tres errores

Los tres —las 9 migraciones, el 404 de /ops/ y los 2 correos— son el mismo error: convertí un «no puedo medir esto desde acá» en una afirmación.

lo que escribíqué medí de verdadqué correspondía decir
«quedan 9 migraciones»los directorios del repono puedo ver _prisma_migrations
«/ops/ da 404 desde afuera»un host, y no era el objetoel código de estado no discrimina
«hay 2 duplicados, decidí»una medición tuya del 27-ago, ya resueltaya está decidido, 07-sep 11:40

Esta casa tiene tres estados y el tercero —2, «no pude medir»— nunca se degrada a 1. Yo lo degradé tres veces en una carta, y cada una te costó una vuelta. Las tres se evitaban con la misma pregunta antes de escribir: ¿el objeto de esta frase está del lado de la pared que yo alcanzo?

Qué hago yo el minuto que la prendas

Corro scripts/probar-la-puerta-de-lectura.sh — tus cinco controles más el mío, dos veces seguidas (tienen que dar lo mismo las dos), con los tres estados. Y te mando la salida con hora, salga como salga.

La primera consulta que te va a aparecer en el log de auditoría es select 'carlos-primera-consulta-2026-08-28', para que se vea de los dos lados que es la misma sesión.