Un operador le dice a Mogui que entró un pago. Mogui pregunta lo que falta y no rellena nada, arma la tarjeta, y con el botón lo publica en #mogos-mogui. Ahí termina: sin fila, sin estado, sin pantalla de admin. Estos son los dieciséis casos — los diez de WhatsApp y los seis de Slack — incluidos los siete en los que algo no sale.
Todos los datos son ficticios: clientes, órdenes, montos, referencias y nombres. Ninguna pantalla sale de la operación real, y esta página es pública.
Cómo leer esta página
16 casos · 2 grupos
¿Qué llega en Slack? Los seis últimos casos son el canal: el mensaje completo, las dos variantes que cambian lo que se lee, el hilo donde alguien lo cierra, y el silencio cuando no llega nada. Ver el mensaje de Slack →
El riel de abajo cambia el caso. Cada uno trae lo mismo, en el mismo orden, para que dos casos se puedan comparar de memoria: la pantalla, qué pasa y por qué, la regla que lo gobierna y la línea de tiempo de lo que ocurre por detrás. Tres casos traen demo viva; el resto nacen completos.
La pantalla
Lo que el operador ve en su teléfono, o lo que el equipo ve en el canal. WhatsApp y Slack son de terceros: sus colores viven acotados y no son tokens de Mogos.
La regla
En verde lima, lo que el flujo sí hace por decisión. En rojo, lo que no hace y no hará: la mitad del diseño es la lista de lo que Mogui se prohíbe.
Por detrás
Quién hace qué, en orden. Las etiquetas dicen de quién es cada paso: APIeveslackhumano
14:04
7
Mogos
en línea
hoy
recibí 150 de María por zelle14:02
Leído. María Ferrer tiene dos órdenes abiertas: el flete MOGOS-F1234 (saldo 150) y la búsqueda COT-20260901-0012 (saldo 480). ¿Cuál abonó?14:02
la primera14:03
Me falta una cosa más: la fecha del Zelle. ¿Hoy?14:03
hoy, ref 8821414:03
1
Pago para reportar
Cliente: María Ferrer · +58 414 200 1188
Orden: flete MOGOS-F1234 · saldo USD 150
Monto: USD 150 · quedaría en 0
Método: Zelle (USD)
Fecha: hoy, 8 de septiembre
Referencia: 88214
Comprobante: no adjunto
Esto no cobra nada: lo publico en #mogos-mogui para que Finanzas lo registre en el admin.
14:02 → 14:04. Dos preguntas, una tarjeta y un botón. Nada se escribió en Mogos.
Caso 01 posted
Dos preguntas, una tarjeta, un mensaje en el canal
El operador manda una frase con la mitad de los datos. Mogui resuelve al cliente, le pregunta al API qué órdenes tienen saldo, y descubre que hay dos. Pregunta cuál, porque elegir por él sería inventar. Con la orden ya sabe el saldo; le falta la fecha, y la pide.
La tarjeta es el único sitio donde el operador ve todo junto antes de que salga. El botón dice «Reportar 150», nunca «Confirmar»: en una tarjeta de pagos, «confirmar» se lee como «confirmar el pago» — que es justo lo que no ocurre.
Desenlace de la tool: posted
Dice en voz alta lo que asumió: «la única abierta», «saldo 150», «quedaría en 0».
Devuelve el enlace al mensaje, construido con el formato de permalink de Slack — sin pedir ámbitos nuevos.
No dice «pago registrado»: no lo está. La orden sigue en 150 y lo dice.
Por detrás5 pasos
1
La compuerta de operador API
Teléfono verificado, rol ADMIN o MANAGER, permiso PAYMENTS:READ y la lista. Firma un JWT de 10 minutos atado a esta conversación.
2
Tres lecturas en paralelo eve
find_client, find_pending_order y list_payment_methods. Ninguna escribe.
3
La tarjeta es una pregunta aparcada eve
ctx.ask en carrera con sleep(2h). El cuerpo de la tool es determinista; el efecto vive en un "use step".
4
Una llamada, sin base de datos API
POST /integrations/slack/mogui/payment-report. Ni lee ni escribe una tabla: publica y devuelve el ts.
5
El mensaje cae en el canal slack
chat.postMessage con el Block Kit del caso 11. El ts vuelve hasta el operador: Mogui dice «listo» porque Slack contestó, no porque llamó.
09:13
7
Mogos
en línea
entró un pago de 15009:11
¿De quién, y a cuál orden?09:11
de María, el flete09:12
Bien: María Ferrer, flete MOGOS-F1234, saldo 150. Me faltan dos cosas: por dónde entró y cuándo. Los métodos activos son Zelle, Pago Móvil y Binance.09:12
zelle, ayer09:13
Listo, con eso ya tengo todo. Va la tarjeta.09:13
Mensaje
Tres rondas, cero suposiciones. Hasta tres faltantes en un mismo mensaje, en prosa.
Caso 02 missing
Pregunta lo que falta, y no rellena nada
Mogui dice qué tiene y qué le falta, no solo qué le falta. Es la diferencia entre «me falta el método» y «bien: María, flete MOGOS-F1234, saldo 150; me faltan dos cosas» — la segunda le deja al operador corregir lo que Mogui entendió mal.
Los métodos que ofrece salen de list_payment_methods, no de la memoria del modelo. Si Finanzas desactiva Binance mañana, la pregunta cambia sola.
Desenlace de la tool: missing
Hasta tres faltantes en un solo mensaje, en prosa. Uno a uno solo cuando la respuesta cambia la siguiente pregunta.
Acepta respuestas adelantadas y en cualquier orden: «zelle, ayer» resuelve las dos.
Nunca rellena: ni la fecha de hoy, ni Zelle porque sea el más común, ni una referencia inventada.
Por detrás4 pasos
1
La tool no llega a llamar eve
Si falta un obligatorio, report_payment devuelve missing y el modelo pregunta. La tool nunca rellena; el modelo nunca inventa.
2
Cada pregunta es una pregunta aparcada eve
Mismo mecanismo que la tarjeta, con vencimiento de 2 h. Vencer no publica nada, y lo dice.
3
Los métodos son los de verdad API
GET /payment-methods?active=true, una vez por sesión: dentro de una conversación no cambian.
4
Tres rondas y suelta humano
Sin dato útil en tres intentos: «Mejor regístralo en el admin», con el enlace. No insiste indefinidamente.
11:40
7
Mogos
en línea
recibí 480 de María11:40
María Ferrer tiene dos órdenes abiertas: · flete MOGOS-F1234 — saldo USD 150 · búsqueda COT-20260901-0012 — saldo USD 480 ¿Cuál abonó?11:40
Mogui no elige, aunque 480 cuadre exacto con una sola de las dos.
Mensaje
El monto que cuadra es una pista, no una decisión.
Caso 03 pregunta
Con dos órdenes abiertas, elige el operador
Es el caso donde la tentación de acertar es mayor: el monto cuadra exacto con la búsqueda, y un modelo servicial elegiría. Aplicar un pago a la orden equivocada deja dos órdenes mal —una cobrada de menos y otra de más— y el error se descubre semanas después, cuando alguien reclama.
Así que Mogui muestra las dos con su saldo y su código, y espera. Que el monto coincida se puede mencionar en una cláusula; no puede decidir.
Muestra las dos con entityCode (MOGOS-F1234), que es el código que el operador tiene delante.
No elige por monto exacto, ni por la más vieja, ni por la más próxima a vencer.
Por detrás2 pasos
1
«Orden» es palabra del operador, no del esquema API
El pago cuelga de una Quotation polimórfica, que no tiene código propio: se reconoce por quotationNumber y sobre todo por el entityCode de lo que factura.
2
La tool devuelve el conjunto y se calla eve
find_pending_order entrega quotationId, quotationNumber, entityType, entityCode, pendingAmount y dueDate. Elegir es del modelo, y el modelo pregunta.
16:23
7
Mogos
en línea
me pagó 200 Ana Torres, pago móvil16:22
Ana Torres no tiene órdenes con saldo en Mogos. ¿Es un adelanto de algo que todavía no está facturado, o es otra clienta?16:22
es adelanto, todavía no le hemos facturado16:23
Entonces no lo puedo reportar contra una orden, y no voy a inventar una. Regístralo en el admin como adelanto, o mándamelo cuando exista la factura. admin.mogos.com/billing16:23
Mensaje
Dos salidas, ninguna inventada. Sin orden no hay reporte.
Caso 04 pregunta
Sin orden con saldo, no hay nada que reportar
Un pago se reporta contra algo. Si el cliente no tiene ninguna orden con saldo, hay dos explicaciones y Mogui no puede distinguirlas: o es un adelanto legítimo, o el operador se equivocó de cliente. Pregunta cuál de las dos, y en ninguno de los dos caminos crea nada.
El mismo corte aplica al cliente que no existe: abrir cuentas es otro oficio. En el flujo de búsquedas Mogui sí crea clientes; aquí no, porque un pago a nombre de una cuenta recién inventada es exactamente el registro que nadie quiere auditar.
Ofrece la salida real: el admin, con el enlace, que es donde se registra un adelanto.
No crea la orden. No crea la cuenta del cliente. No aplica el pago «a la próxima que aparezca».
Por detrás2 pasos
1
La lectura devuelve cero API
GET /payments/admin/pending/:userId con lista vacía. No es un error: es un dato, y el guion lo trata como tal.
2
Ninguna tool de escritura se toca eve
create_client existe en Mogui para búsquedas y no está disponible en el skill de pagos. Lo que no se expone no se puede llamar por error.
10:06
7
Mogos
en línea
150 de María para el flete, zelle, hoy10:05
Ojo con una cosa: son 150 y el saldo del flete MOGOS-F1234 es 120. ¿Quedan 30 a favor, o el monto es otro?10:05
quedan a favor, ya le avisé10:06
Pago para reportar
Cliente: María Ferrer · +58 414 200 1188
Orden: flete MOGOS-F1234 · saldo USD 120
Monto: USD 150 · sobran 30 a favor
Método: Zelle (USD)
Fecha: hoy, 8 de septiembre
Esto no cobra nada: lo publico en #mogos-mogui para que Finanzas lo registre.
Vence en 2 h · «Corregir» o «cambiar monto 120»
10:06
Reportar 150
Corregir
Cancelar
Mensaje
Avisa y sigue. El sobrante va en la tarjeta y en el mensaje del canal.
Caso 05 over_balance
Cobrar de más es raro, pero es legítimo
Un abono mayor al saldo pasa: un adelanto, una orden que se amplió, un cliente que redondea. La tool lo devuelve y no bloquea: rechazarlo sería la tool decidiendo un caso de negocio que le corresponde a Finanzas.
Lo que sí hace es negarse a que pase inadvertido. El sobrante aparece marcado en la tarjeta antes del botón, y viaja al mensaje de Slack, porque quien lo registre necesita saberlo antes de teclear el monto.
Desenlace de la tool: over_balance
Lo dice en una cláusula y ofrece la corrección en el mismo mensaje: «¿o el monto es otro?».
El sobrante queda marcado en la tarjeta y en el campo Monto del mensaje del canal.
No lo rechaza, no lo recorta al saldo, y no lo reparte entre las otras órdenes abiertas.
Por detrás2 pasos
1
El saldo viene del API, no de una resta del modelo eve
pendingAmount es lo que devolvió find_pending_order en ese instante. La comparación la hace la tool en código, no el modelo en prosa.
2
La decisión queda donde debe humano
Aplicar un sobrante es trabajo de Finanzas en el admin, con los controles que ya tiene.
08:30
7
Mogos
en línea
recibí 90 de Pedro Luna por pago móvil08:30
Los pagos los reporta un administrador, y tu usuario en Mogos es de asesor. Pásaselo a Finanzas o pídele a Carlos que lo reporte — yo no lo publico.08:30
Ninguna consulta salió: la tool comprueba rol y permiso antes de llamar al API.
Mensaje
Se corta antes de la primera llamada. Ni el saldo de Pedro se llegó a consultar.
Caso 06 forbidden
Un asesor entra a Mogui, pero no a los pagos
Hoy operator-auth.ts tiene un permiso único y global — SERVICE_QUOTES:CREATE, con roles ADMIN, MANAGER y AGENT. Correcto para búsquedas y demasiado ancho para pagos: las lecturas de este flujo son @CanRead('PAYMENTS'), y publicar el saldo de un cliente en un canal es un acto sobre datos financieros de terceros.
Si el permiso sigue siendo uno solo, un asesor puede exponer en Slack los saldos de cualquier cliente. Por eso el permiso pasa a ser por oficio, comprobado en la tool.
Desenlace de la tool: forbidden
Contesta con la salida —«pásaselo a Finanzas»— en vez de un «no tienes permiso» a secas.
No consulta el saldo, no resuelve al cliente, no gasta la llamada para que el API conteste 403.
Por detrás3 pasos
1
Capa 1 · el canal eve
Decide si el operador entra a Mogui: teléfono verificado, rol de equipo y la lista MOGUI_OPERATOR_PHONES.
2
Capa 2 · la tool eve
Decide si este oficio es suyo: ADMIN o MANAGER, más PAYMENTS:READ. Es la capa que hoy no existe.
3
Capa 3 · el API API
Reaplica RBAC sobre X-Mogos-Acting-User. Es la que manda; las dos anteriores solo evitan el viaje.
14:17
7
Mogos
en línea
hoy
150 de María, zelle, hoy, para el flete14:02
Publicado en #mogos-mogui.14:04
oye, entró el de María — 150 por zelle14:16
1Ese ya lo reportaste hace 12 minutos y está en el canal: María Ferrer, flete MOGOS-F1234, USD 150, Zelle, hoy. ¿Es otro pago distinto?14:16
Es otro pago
No, era el mismo
Tú
Ese ya lo reportaste hace 12 minutos
No, era el mismo14:17
Listo, no publiqué nada.14:17
Mensaje
El mismo operador, el mismo día. La huella vive en el estado de la sesión.
Caso 07 lo ve el guion
El duplicado que sí se atrapa — y el que no
Sin base de datos hay dos capas contra el duplicado, y las dos son sólidas. La primera es el paso durable de eve: publicar ocurre dentro de un "use step", y el mundo Postgres del agente memoiza su resultado. Si el workflow se reejecuta, el paso devuelve el ts que ya tenía en lugar de publicar otra vez. Eso no es una tabla nueva: es la propiedad por la que se eligió un mundo durable en lugar de disco.
La segunda cubre al mismo operador repitiéndose, que es el caso frecuente —le mandan el aviso del banco dos veces y lo reenvía dos veces— y al implementarla resultó que no cabe en el código: una workflow tool no puede alcanzar el estado de la sesión, porque `eve build` empaqueta su cuerpo sin builtins de Node y `defineState` importa `node:async_hooks`. Así que la vigila el guion, que sí tiene la conversación entera delante. Es más débil que un índice, y por eso se dice.
Desenlace de la tool: — · lo vigila el guion, no el código
Repite los cinco datos del reporte anterior, para que el operador vea si de verdad es el mismo.
Si dice que es otro, publica y añade una línea que lo enlaza al anterior.
No decide por él. «Parecido» no es «el mismo».
Lo que esta pieza no puede dibujar. Si Carlos y Ana ven la misma notificación del banco y los dos se la pasan a Mogui, salen dos mensajes al canal y nada los cruza. La red de seguridad real está más abajo: el dinero solo se duplica si Finanzas registra dos veces en el admin, y ahí sí hay una fila que lo desmiente. Lo que se pierde no es la seguridad del dinero; es el ruido en el canal.
Por detrás3 pasos
1
Capa 1 · el paso durable eve
@workflow/world-postgres, ya desplegado. Cubre el 100 % de los duplicados técnicos: reintentos, redeploys, la carrera resolviéndose dos veces.
2
Capa 2 · el guion, no el código humano
Una workflow tool no puede tocar el estado de la sesión: eve build empaqueta su cuerpo sin builtins de Node y defineState importa node:async_hooks. Lo vigila el skill reportar-pago, mirando la conversación. Más débil que un índice, y por eso se dice.
3
Capa 3 · no existe humano
Dos operadores distintos reportando la misma transferencia no se cruzan. Sus sesiones son distintas y no hay índice contra el que chocar.
16:03
7
Mogos
en línea
Pago para reportar
Cliente: María Ferrer · +58 414 200 1188
Orden: flete MOGOS-F1234 · saldo USD 150
Monto: USD 150 · quedaría en 0
Método: Zelle (USD)
Fecha: hoy, 8 de septiembre
Esto no cobra nada: lo publico en #mogos-mogui para que Finanzas lo registre.
Vence en 2 h · «Corregir» o «cambiar monto 120»
14:03
Reportar 150
Corregir
Cancelar
· dos horas después ·
Pasaron 2 h sin respuesta, así que no publiqué nada. Si el pago entró, mándamelo otra vez.16:03
Mensaje
Vencer no publica, y lo dice. El silencio nunca es un sí.
Caso 08 expired
Vencer no publica nada, y Mogui lo dice
La tarjeta es ctx.ask en carrera con sleep("2h"). Si gana el reloj, la tool devuelve expired y no llama a nada. Lo importante no es que no publique: es que lo anuncie. Un vencimiento silencioso deja al operador creyendo que reportó.
«Cancelar» es el mismo camino con otro nombre, y produce la misma frase. Los dos desenlaces terminan con el operador sabiendo exactamente qué pasó.
Desenlace de la tool: expired · cancelled
Avisa cuando vence, con la salida: «mándamelo otra vez».
El TTL es MOGUI_CONFIRM_TTL, el mismo que ya usa la tarjeta de búsquedas.
No publica «por si acaso». No reintenta sola. No guarda un borrador en ningún sitio.
Por detrás2 pasos
1
La carrera eve
Promise.race([ctx.ask(…), sleep(ttl)]). Si ask no resuelve, el resultado es undefined y ese es el desenlace expired.
2
No queda borrador eve
Sin fila en Mogos, un reporte no confirmado no existe en ninguna parte. Es coherente con la decisión: lo único que persiste es lo publicado.
10:42
7
Mogos
en línea
150 de María, zelle, el 12 de octubre10:41
Esa fecha no me sirve: el 12 de octubre todavía no llegó, y un pago no puede ser futuro. ¿Fue hoy, 8 de septiembre?10:41
sí perdón, hoy10:42
Perfecto. Va la tarjeta.10:42
Mensaje
Un rechazo llega como pregunta. Nunca como «falló algo».
Caso 09 rejected
Cuando Mogos dice que no, el operador oye una pregunta
Esta es la lección que ya se pagó en el flujo de búsquedas. La primera versión de create_search convertía cualquier fallo en «el API respondió 400», y el guion contestaba «no pude crearla: el sistema no respondió, reenvíamela en unos minutos» — que tapaba el único dato útil (qué campo) y daba un consejo falso: un 400 no mejora con el tiempo.
El traductor apiFailure() ya existe y separa lo reintentable (408, 429, 5xx, red) de lo que es una decisión (400, 401, 403, 404), conservando el campo rechazado entero. Aquí se reusa tal cual.
Desenlace de la tool: rejected
Nombra el campo y propone el valor correcto: «¿fue hoy, 8 de septiembre?».
No reintenta un 4xx. No dice «en unos minutos» cuando reintentar no puede funcionar.
Por detrás3 pasos
1
Zod corta antes de gastar la llamada eve
La fecha futura la rechaza el esquema de la tool, con el mismo límite que @IsRecentPast: 365 días atrás, nunca futura.
2
Y si pasa el esquema, el DTO lo dice entero API
MogosApiError.details y .fields conservan cada mensaje del pipe de NestJS y extraen el campo de su primer token.
3
El traductor lo vuelve pregunta eve
explainFailure() convierte campos en preguntas. Es código que ya está escrito y probado.
14:04
7
Mogos
en línea
Pago para reportar
Cliente: María Ferrer · +58 414 200 1188
Orden: flete MOGOS-F1234 · saldo USD 150
Monto: USD 150 · quedaría en 0
Método: Zelle (USD)
Esto no cobra nada: lo publico en #mogos-mogui para que Finanzas lo registre.
Vence en 2 h
14:03
Reportar 150
Corregir
Cancelar
Tú
Pago para reportar · María Ferrer
Reportar 15014:04
1No pude publicarlo: Slack no contestó. El pago no quedó reportado en ningún sitio — ni en el canal ni en Mogos.
El fallo viaja hasta el operador. Es la única recuperación que hay.
Caso 10 slack_failed
Sin fila que reintente, el fallo tiene que llegar a una persona
Aquí está la inversión más importante que trae la decisión de no tener base de datos. El notifier de sugerencias que ya existe en producción se traga toda excepción a propósito: una caída de Slack no puede volver la sugerencia de un cliente en un error 500, y hay una fila IntegrationDelivery que registra el fallo para que alguien lo vea después.
En este flujo no hay esa fila. Si el fallo se traga, el pago desaparece: nadie lo sabe, nadie lo reintenta, nadie lo registra. Así que el contrato se invierte — el error sube hasta el operador en el mismo turno, con la salida concreta.
Desenlace de la tool: slack_failed
Dice las dos verdades: no está en el canal y no está en Mogos.
Ofrece el enlace al admin, que es donde el pago sí se puede registrar de verdad.
No dice «lo intento de nuevo luego»: no hay nada que lo intente de nuevo.
Por detrás3 pasos
1
La ruta no traga API
POST /integrations/slack/mogui/payment-report devuelve el motivo. Es lo contrario del contrato de SlackSuggestionNotifier, y a propósito.
2
El desenlace es de primera clase eve
slack_failed está en la unión de tipos de la tool, no es una excepción escapada. Lo que el guion tiene que decir está escrito, no improvisado.
3
El canal mal configurado da 503 API
Sin INTEGRATION_SECRET_SLACK_CHANNEL_ID_MOGUI la ruta responde 503 y lo dice al arrancar. Nunca cae al canal de sugerencias.
Buscar en Mogos Group
#mogos-moguiLo que Mogui reporta desde WhatsApp · nadie cobra desde acáMA9
⚠️No cobrado. La orden sigue en USD 150 y Caja no se movió: esto es lo que dijo el operador. Para que cuente, hay que registrarlo en el admin.
Ver comprobanteRegistrar en el admin
Por WhatsApp · hace un momento · 👀 para marcar que lo viste · responde en el hilo si algo no cuadra
Enviar mensaje a #mogos-mogui
#mogos-mogui, un martes cualquiera. El mensaje completo, como lo ve Finanzas.
Caso 11 el mensaje
Esto es lo que llega al canal
Seis campos, la frase textual del operador y una advertencia. Con eso, quien lo lea decide sin abrir otra pantalla si el pago cuadra, y si no, tiene el hilo para preguntar.
La cita textual va entera: es la única prueba de qué se dijo exactamente, y es lo que salva el caso en que los campos y la frase no coinciden — porque entonces el que se equivocó fue Mogui al interpretar, no el operador al escribir.
El aviso push lleva cliente, monto, método y quién lo reportó: con eso se decide si abrir Slack ahora o después.
«Registrar en el admin» es primario y lleva la orden y el monto en la query: es el trabajo que el mensaje pide.
No hay código de reporte, porque no hay reporte. El identificador del mensaje es su propio permalink.
Los botones no se pintan de marino ni de verde WhatsApp. El verde del primario es de Slack, y es suyo.
El aviso es un bloque section, no un context. Se dibujó como context en el primer borrador y al verlo en la ventana quedó en gris de 13 px — el tamaño de un pie de foto para la única frase que impide dar la orden por cobrada. Block Kit no sabe pintar cajas de color, así que el peso lo dan el tamaño del section y el ⚠️.
Por detrás3 pasos
1
El builder es puro API
Payload dentro, bloques fuera. Cero I/O, cero Nest, cero reloj — igual que suggestion-blocks.builder.ts, que es lo que lo hace comprobable sin red.
2
La cita va escapada API
&, < y >: un operador que escriba <@U123> no puede convertirse en una mención en un canal que lee todo el equipo.
3
Un botón de enlace no es solo un enlace slack
Textual de Block Kit: «If you're using url, you'll still receive an interaction payload and will need to send an acknowledgement response». Sin Interactivity Request URL el enlace abre y el usuario ve un error.
Buscar en Mogos Group
#mogos-moguiLo que Mogui reporta desde WhatsApp · nadie cobra desde acáMA9
150 de María para el flete, zelle, hoy — quedan 30 a favor, ya le avisé
⚠️No cobrado. El monto es mayor que el saldo: la orden debe USD 120 y el operador reporta USD 150. Quien lo registre decide qué hacer con los 30.
Ver comprobanteRegistrar en el admin
Por WhatsApp · hace un momento · 👀 para marcar que lo viste · responde en el hilo si algo no cuadra
Enviar mensaje a #mogos-mogui
Caso 05, visto desde el canal. El sobrante viaja del teléfono al mensaje.
Caso 12 over_balance
Cuando entra más de lo que se debe, el canal lo dice primero
Es la variante que más se agradece leyendo. El campo Monto aparece solo cuando el pago no cuadra con el saldo, marcado, y la advertencia cambia de texto: en vez del «no cobrado» genérico, dice exactamente qué no cuadra y quién decide.
Sin este renglón, alguien teclea 150 en el admin contra una orden de 120 y el sistema lo rechaza — o peor, lo acepta y deja 30 flotando sin explicación. El aviso llega antes que el problema.
El campo Monto solo existe cuando hay discrepancia: en el caso normal el monto ya está en el encabezado y repetirlo sería ruido.
La advertencia dice quién decide («quien lo registre»), no qué hacer: aplicar un sobrante es criterio de Finanzas.
No propone repartirlo entre las otras órdenes abiertas del cliente.
Por detrás2 pasos
1
La comparación es código, no prosa eve
pendingAmount viene de find_pending_order; la tool compara y devuelve over_balance. El modelo redacta, no calcula.
2
El builder ramifica una sola vez API
Un campo extra y un texto de aviso distinto. Dos plantillas separadas se habrían desincronizado en la primera corrección.
Buscar en Mogos Group
#mogos-moguiLo que Mogui reporta desde WhatsApp · nadie cobra desde acáMA9
me pagó María los 150 del flete, en efectivo, hoy en la oficina
⚠️No cobrado. La orden sigue en USD 150 y Caja no se movió: esto es lo que dijo el operador. Para que cuente, hay que registrarlo en el admin.
Registrar en el admin
Por WhatsApp · hace un momento · 👀 para marcar que lo viste · responde en el hilo si algo no cuadra
Enviar mensaje a #mogos-mogui
Efectivo en la oficina. Sin banco no hay referencia, y sin foto no hay botón, el mensaje lo dice en vez de callarlo.
Caso 13 sin comprobante
Un pago en efectivo no trae foto, y el mensaje no la inventa
El botón «Ver comprobante» no está: aparece solo cuando el reporte trae media. Un botón que lleva a una imagen que no existe es peor que ninguno — es la misma regla que ya gobierna el botón de WhatsApp en la propuesta de sugerencias, donde solo sale si el teléfono está verificado.
Y los campos vacíos se rinden con una raya, no desaparecen. Un campo en «—» es un dato («no hay referencia»); un campo ausente es un hueco que el lector rellena solo, normalmente suponiendo que sí había y que alguien lo perdió.
«Comprobante: no adjunto» aparece como campo propio, para que la ausencia sea explícita.
Es justo el caso en el que el efectivo más necesita un testigo: el mensaje en el canal es el único papel.
No se pone el botón deshabilitado: en Slack un botón gris sigue disparando una interacción y confunde igual.
Por detrás2 pasos
1
El builder omite, no deshabilita API
Sin proofUrl el elemento no entra en el arreglo de elements. Un actions vacío tampoco se emite: Slack lo rechazaría.
2
Y cuando sí hay foto, es un enlace API
Nunca un bloque image: se resuelve al postear y Slack cachea, así que la firma de S3 vence y queda una imagen rota. Se firma al leer (commit 2f2aebb2).
Buscar en Mogos Group
#mogos-moguiLo que Mogui reporta desde WhatsApp · nadie cobra desde acáMA9
⚠️No cobrado. La orden sigue en USD 150 y Caja no se movió: esto es lo que dijo el operador. Para que cuente, hay que registrarlo en el admin.
Ver comprobanteRegistrar en el admin
Por WhatsApp · hace 14 min · 👀 para marcar que lo viste
👀 2✅ 1+
AM1 respuestaAna Millán · hace 6 min
AM
Ana Millán14:18
Registrado. El Zelle aparece en el estado de cuenta con la ref 88214, así que le apliqué los 150 completos al flete — MOGOS-F1234 quedó en 0.
Enviar mensaje a #mogos-mogui
14:18, cuatro minutos después. Dos 👀, un ✅ y una respuesta que dice qué se hizo.
Caso 14 reacción · hilo
El pago no está cobrado hasta que alguien lo diga en el hilo
Este es el caso que cierra el círculo, y el único sitio donde consta que el dinero llegó a Caja. Sin base de datos, el hilo es el registro: Ana dice qué hizo, no «ok», y por eso el mensaje siguiente que alguien lea ya sabe que no hay nada pendiente.
Un botón «Visto» de verdad obligaría a darle cuerpo a POST /integrations/slack/interactivity, que hoy solo devuelve 200 — y en este diseño no tendría dónde escribir el visto. La reacción 👀 ya está cableada (reactions:read concedido, slack-reaction-actions.ts escrito) y el equipo ya la usa por instinto.
El pie del mensaje enseña el protocolo, para que 👀 sea convención y no costumbre de tres personas.
La respuesta va en el hilo, pegada al pago, en vez de perderse suelta en el canal.
No hay botón «Visto». Un botón que no recuerda nada es peor que ninguno.
Por detrás3 pasos
1
La reacción ya llega slack
El ámbito reactions:read está concedido desde el día uno. Exige que el bot esté invitado al canal: sin membresía no llega ni una reacción.
2
El endpoint de interactividad se queda como está API
Contesta 200 y nada más. Es justo lo que hace seguro sacar esta fase con botones de enlace y sin tocarlo.
3
El hilo es el cierre humano
Convierte «alguien lo vio» en «alguien lo registró» sin que ninguna tabla lo garantice. Es una convención, y hay que decirla en voz alta para que se sostenga.
Buscar en Mogos Group
#mogos-moguiLo que Mogui reporta desde WhatsApp · nadie cobra desde acáMA9
No hay nada nuevo en #mogos-moguiEl pago de María se reportó a las 14:04 y este canal no se enteró. La única persona que lo sabe es Carlos, en su teléfono.
Enviar mensaje a #mogos-mogui
El caso 10, visto desde el otro lado. Un silencio que nadie distingue de «no pasó nada».
Caso 15 sin mensaje
Cuando falla, el canal se ve igual que un día tranquilo
Este caso no tiene pantalla propia, y esa es la pantalla. Un canal sin mensajes nuevos es indistinguible de uno donde algo se perdió: nadie nota la ausencia de un mensaje que no sabe que debía existir.
Por eso la recuperación no vive aquí, donde no hay nadie mirando, sino en el caso 10: Mogui se lo dice al operador en el mismo turno, mientras todavía tiene el teléfono en la mano y el dato fresco. Es la única persona del sistema que sabe que faltó un mensaje.
El operador se entera al instante, y con la salida concreta: registrarlo él en el admin.
Nadie lo reintenta. No hay outbox, no hay cron, no hay fila en PENDING.
El canal no muestra ningún hueco, ni puede: un mensaje que no salió no deja rastro.
Por detrás3 pasos
1
Tres formas de fallar, una sola respuesta API
Slack caído, token revocado, o el bot fuera del canal (not_in_channel). Las tres suben el motivo hasta el operador.
2
El canal sin configurar da 503 API
Sin INTEGRATION_SECRET_SLACK_CHANNEL_ID_MOGUI la ruta responde 503 y lo dice al arrancar, para que se descubra en el despliegue y no en el primer pago.
3
Si esto molesta, hay tabla humano
Reintentar de verdad exige una fila que recuerde el intento — exactamente lo que esta versión decidió no tener.
Buscar en Mogos Group
#mogos-moguiLo que Mogui reporta desde WhatsApp · nadie cobra desde acáMA9
⚠️No cobrado todavía. La orden sigue en USD 150. Los dos botones verdes de abajo son los que no existen hoy.
Confirmar y registrarRechazarVer comprobante
Al apretar: chat.update reescribe el mensaje sin botones — «✅ Confirmado por Ana Millán · PAY-20260908-0311 · la orden MOGOS-F1234 quedó en USD 0»
Enviar mensaje a #mogos-mogui
Futuro, no plan. Los botones existen en el dibujo para cerrar la pregunta.
Caso 16 futuro
El botón que mueve dinero, y por qué no está en el plan
Vale la pena dibujarlo para cerrar la pregunta. Confirmar desde Slack no necesita la tabla de reportes: el mensaje puede llevar el objeto congelado en private_metadata, y confirmar sería llamar a POST /payments/admin/register — el servicio que ya existe, con su @Audit y su Log.
Es la razón por la que los campos de la tool son, campo por campo, los de RegisterAdminPaymentDto. No porque hoy los consuma nadie, sino porque es una decisión gratis ahora y cara de deshacer después.
Lo que sí exige, y no se puede saltar: un mapa slack_user_id → User.id. Quien aprieta es un U01ABC, no un usuario de Mogos, y un efecto de dinero no puede ejecutarse en nombre de nadie. Ese mapa es una tabla — diminuta, pero tabla. Y no tener base de datos fue la decisión.
Al resolver, chat.update retira los botones: un mensaje con botones vivos sobre un pago ya registrado es la forma más fácil de registrarlo dos veces.
«Rechazar» abre un modal que pide la razón — un rechazo sin razón no le sirve al operador que lo reportó.
No está planificado. Está escrito para que la primera vez que se pida, la respuesta no sea un diseño desde cero.
Lo que haría falta3 pasos
1
El mapa de identidad API
slack_user_id → User.id, poblado a mano por un admin. Sin fila, el botón contesta en efímero: «tu usuario de Slack no está enlazado a Mogos».
2
El cuerpo del endpoint de interactividad API
Verificar firma, resolver la acción, acusar en menos de 3 s y trabajar en setImmediate. Slack reintenta tres veces, así que el procesador tiene que ser idempotente.
3
Ningún ámbito nuevo slack
chat.update y views.open los cubre chat:write. La barrera no es Slack: es la identidad.
Las tres frases que sostienen el diseño
la disciplina, sin esquema
La versión anterior de este diseño tenía una tabla payment_reports con estados, un código PR-… y una bandeja en el admin. Carlos la descartó entera: «NO hay admin. NO hay PaymentReport. NO hay base de datos. Solo: WhatsApp 1:1 → preguntas/resumen → mensaje a Slack y listo.»
Eso quita cinco piezas y una migración, y hace que el flujo quepa en una sola fase. Pero mueve algo de sitio: lo que antes garantizaba el esquema ahora lo garantiza la redacción. Sin un estado REPORTADO en una pantalla, la única cosa que impide que alguien dé una orden por cobrada es que las tres superficies lo digan — con las mismas palabras, las tres veces.
En la tarjeta
«Esto no cobra nada: lo publico en #mogos-mogui para que Finanzas lo registre en el admin.» Antes del botón, no después.
En el mensaje
«No cobrado. La orden sigue en USD 150 y Caja no se movió.» Un bloque propio, no una nota al pie: si algo se cae por espacio, se cae otra cosa.
Al confirmar
«La orden sigue en 150 hasta que Finanzas lo registre.» El último mensaje que lee el operador no puede sonar a cobro cerrado.
Y una cuarta, que es una renuncia. Dos operadores que reporten la misma transferencia producen dos mensajes, y nada los cruza (caso 07). La red que sí detiene el doble cobro está más abajo: el dinero solo se duplica si Finanzas lo registra dos veces en el admin. Lo que se pierde no es la seguridad del dinero — es el ruido en el canal.