Operator rejection policy — curated reasons & internal notes
Purpose: the domain truth for what an operator may say to a merchant when a queue item is rejected — a curated, merchant-facing reason, never the operator’s free text. Independent of how any single queue is built. Audience: AI agents (and the platform operators the runbook trains). Status: as of 2026-09-20. Approved by the PO 2026-09-19 as part of Operator Queues (sub-project 2 —
multi-platform-roles-admin-operator, § “Sub-project 2 — Operator queues”). Companion daily flow:operator-queues.
Why this exists
Section titled “Why this exists”Every reject action across the operator queues used to have the same trap: the operator typed a free-text reason and — on the liquidaciones side — that free text was shown to the merchant verbatim (the finding that motivated this policy). On the bank-account side the opposite failure applied: the operator’s reason was audit-only, so a rejected merchant saw only the generic “tu cuenta fue rechazada, vuelve a capturar” (web #911) and re-entered the same wrong account because nobody told them what was actually wrong. Both failures are fixed the same way: split what the operator records into two fields with two different audiences.
- Every reject is
reasonCode+ optionalinternalNote. The operator picks a curatedreasonCodefrom the queue’s catalogue (a<select>, never free text). The merchant sees only that code’s es-MX copy. The operator may additionally write aninternalNote— free text for platform staff. - The curated reason is the ONLY thing a merchant ever sees. No merchant-facing surface (web card, mobile screen, email/push notification, status projection, API read model) renders the operator’s
internalNote. The note lives inaudit_logalongside the actor + before/after, and stops there. This is an invariant every queue’s tests must assert (cf. the Web draft’s AC3). internalNoteis audit-only and platform-only. It exists so staff can record why — the specific document that was blurry, the exact bank that bounced the transfer, the ticket number — without leaking vendor names, internal process, or timeframes to the merchant.- “Otro” (
other) requires aninternalNote. Whenever the operator falls back to the catch-all reason, the note is mandatory (there is no curated copy to explain the rejection, so there must be an internal record). The merchant sees the generic line “Contacta a soporte para más información.” — never the note. - Curated copy is es-MX, actionable, and neutral. Each reason’s merchant copy says what to fix in plain Spanish. It carries no vendor/PSP names, no bank names beyond “admitido / no admitido”, no timeframes (“en 24 h”, “en 2 días hábiles”), and no internal process detail. Mechanism only.
- The catalogue and its copy are product-owned, not operator-editable. This document is the source of truth for the curated sets. An operator can only select a code and write a note — they cannot add, rename, or reword a reason. Changing the set or its copy is a product decision (PO) landed as a docs PR against this file, mirrored into the enum the API and Web read. Operators propose changes through the PO; they do not ship copy.
- Codes are stable identifiers; copy is a translation of the code. The
reasonCode(e.g.clabe_invalid) is what is stored and audited; the es-MX string is a presentation-layer lookup keyed by the code, so copy can be reworded in one place without a data migration.
The three catalogues
Section titled “The three catalogues”Each queue that rejects has its own curated set. other is present in all three, always requires a note, and always renders the same generic merchant line. The codes below are the canonical identifiers; the API enum and the Web <select> mirror them.
Cuentas bancarias — bank-account verification
Section titled “Cuentas bancarias — bank-account verification”reasonCode |
Merchant-facing es-MX copy |
|---|---|
holder_mismatch |
El titular de la cuenta no coincide con el comercio. Sube una cuenta a nombre del comercio. |
clabe_invalid |
La CLABE no es válida. Verifica los 18 dígitos y vuelve a capturarla. |
bank_not_supported |
El banco de la cuenta no está admitido. Registra una cuenta de un banco admitido. |
illegible_document |
El documento no es legible o está incompleto. Sube una carátula clara y completa. |
statement_mismatch |
La carátula no corresponde a la cuenta capturada. Sube la carátula de esa misma cuenta. |
other |
Contacta a soporte para más información. (nota interna obligatoria) |
Liquidaciones — settlement request
Section titled “Liquidaciones — settlement request”reasonCode |
Merchant-facing es-MX copy |
|---|---|
bank_rejected |
El banco rechazó la cuenta destino. Revisa los datos de tu cuenta y vuelve a solicitar. |
account_outdated |
Los datos de la cuenta están desactualizados. Actualiza tu cuenta bancaria y vuelve a solicitar. |
under_review |
El movimiento está en revisión. No necesitas hacer nada por ahora. |
other |
Contacta a soporte para más información. (nota interna obligatoria) |
Datos fiscales — business / tax verification
Section titled “Datos fiscales — business / tax verification”reasonCode |
Merchant-facing es-MX copy |
|---|---|
rfc_name_mismatch |
El RFC no coincide con la razón social. Corrige tus datos fiscales. |
rfc_inactive |
El RFC no está vigente. Verifica tu situación fiscal y actualiza tus datos. |
fiscal_incomplete |
Tus datos fiscales están incompletos. Completa la información faltante. |
other |
Contacta a soporte para más información. (nota interna obligatoria) |
Edge cases
Section titled “Edge cases”- Operator submits
otherwith no note → the queue rejects the submission (validation error). “Otro” without a note is never persisted. - Operator writes an
internalNotebut picks a real curated code → allowed and encouraged; the note is stored, the merchant still sees only the curated copy, never the note. - A rejection reason exists in the model but not in this catalogue → treat it as unmapped and render the generic
othercopy to the merchant rather than leaking a raw slug (same defensive posture as the verification_fallbackmapping, decisions 2026-09-02). Then file a docs change to add the code. - Identity checks are locked to Admin. Operators reject business/fiscal checks (this
Datos fiscalescatalogue); identity revoke/unlock/attest is an Admin action and out of this policy’s operator scope — see the runbook’s escalation section.
Open questions
Section titled “Open questions”- Final wording of each es-MX string is PO-confirmable; the codes are fixed. Any change is a docs PR against this file. Mirror unresolved items in
../progress/open-questions-and-todos.md.