E5a — Datos y documentos del pago: diseño técnico
Date: 2026-08-07
⚠️ Actualización 2026-08-10: la verificación de PIN que describe este documento se implementó y después se sacó. Carlos la probó y encontró que, buscando por número de transacción —el camino habitual, porque el cliente llega con la carta—, pedía retipear el número que se acababa de tipear. El resto del diseño (campos del pago, documentos, papeleta) sigue vigente tal cual. El razonamiento completo está en Pago con documentos §1.2. Fuente: análisis de las definiciones de CIS-EC (requerimientos #1, #4 y #5, y la hoja Módulo caja). Reemplaza al alcance de E5.1, E5.2 y E5.4 del plan v2.
Por qué se parte E5
El plan v2 describía E5 como "wizard de tres uploads + papeleta". El material de CIS-EC lo vuelve bastante más grande: el operador no solo sube documentos, completa datos de la transacción que Soterex no manda y que después van al reporte regulatorio. Y el medio de pago deja de ser solo efectivo.
Se parte en dos para que la papeleta —que necesita que estos datos existan— no arrastre el riesgo de la parte de captura:
- E5a (este documento): campos que completa el operador, medio de pago, y los documentos. (El PIN se sacó — ver la advertencia de arriba.)
- E5b: papeleta y formulario de enrolamiento, ambos con modelo Word ya provisto.
1. Modelo de datos
1.1 transactions (ALTER)
Campos que hoy no existen y el reporte regulatorio necesita. Todos nullable: una transacción ACCEPTED recién ingresada no los tiene — se completan al pagar.
| Columna | Tipo | Origen | Nota |
|---|---|---|---|
pin | — | — | No se agrega — corregido el 2026-08-07. El PIN de la carta es el MTCN que la app genera y le responde a Soterex. En la carta figura dos veces: MTCN: C-5436014032 y Payment PIN: 5436014032. Se verifica contra mtcn tolerando el prefijo C-; guardarlo aparte duplicaba un dato que ya teníamos |
expires_at | timestamp nullable | Soterex / derivado | La carta indica 60 días para retirar |
nationality | string nullable | Soterex | Distinto de destination_country |
beneficiary_address | string nullable | CIS al pagar | |
beneficiary_city | string nullable | CIS al pagar | |
beneficiary_state | string nullable | CIS al pagar | |
identification_type | string nullable | CIS al pagar | |
identification_country | char(2) nullable | CIS al pagar | |
identification_number | string nullable | CIS al pagar | |
phone_2 | string nullable | CIS al pagar | "SOTEREX ENVÍA UN TELÉFONO, AGREGAR UN TELÉFONO 2" |
email_2 | string nullable | CIS al pagar | "SOTEREX ENVÍA UN MAIL, AGREGAR UN MAIL 2" |
delivery_type | string nullable | CIS al pagar | EFECTIVO | TRANSFERENCIA |
bank_account | string nullable | CIS al pagar | Solo con TRANSFERENCIA |
notes | text nullable | CIS al pagar | "gestiones de contacto adicional" |
Lo que NO se agrega:
Payout Currency,Ex. RateyBeneficiary Destination Amountdel reporte. Con el cambio de moneda diferido a MVP2, los tres son derivables (USD,1.0, y el principal). Agregarlos ahora sería guardar tres constantes. Se resuelven en el reporte.
1.2 transaction_documents (tabla nueva)
id uuid pk
transaction_id uuid fk transactions, not null
type varchar -- ver catálogo abajo
disk varchar -- 'documents' (local en dev, s3 en staging/prod)
path varchar -- ruta relativa, NUNCA una URL
original_name varchar
mime_type varchar
size_bytes integer
uploaded_by uuid fk users, not null
created_at timestampCatálogo de tipos (constantes en el modelo, mismo criterio que los motivos de caja):
| Tipo | Obligatorio | Aplica a |
|---|---|---|
CARTA | Sí | Todos |
IDENTIFICACION_ANVERSO | Sí | Todos |
IDENTIFICACION_REVERSO | Sí | Todos |
SELFIE | No — "DESEABLE, no es obligatorio" | Todos |
PAPELETA_FIRMADA | Sí | Todos |
FORMULARIO_ENROLAMIENTO | Sí | Solo TRANSFERENCIA |
CARTOLA_BANCARIA | Sí | Solo TRANSFERENCIA |
SOLICITUD_TRANSFERENCIA | Sí | Solo TRANSFERENCIA |
CONFIRMACION_ACREDITACION | Sí | Solo TRANSFERENCIA |
Sin updated_at: un documento no se edita. Reemplazarlo es subir otro; el anterior queda.
1.3 El estado "pagada pero incompleta"
La hoja Módulo caja lo plantea como posibilidad: "Podría evaluarse la posibilidad de que la tx se quede pagada pero incompleta, si todos los soportes no se han guardado".
No se agrega un estado nuevo. status seguiría siendo PAID, y "incompleta" se deriva de si faltan documentos obligatorios para su delivery_type. Un estado nuevo obligaría a revisar cada consulta que hoy filtra por status, para representar algo que es una pregunta sobre otra tabla.
Se expone como documentacion_completa (booleano derivado) y como filtro en el listado.
2. Almacenamiento
Los documentos son PII sensible: identificaciones y fotos de personas migrantes. El manejo es el punto más delicado de esta entrega.
- Disco
documents, configurado por ambiente:localen dev, S3 privado en staging y producción. El bucket va a Terraform con acceso público bloqueado y cifrado at-rest. - Nunca se sirve una URL directa. La descarga pasa siempre por un endpoint propio que valida permiso y alcance por país/PDV, y audita cada acceso (
DOCUMENT_VIEWED). pathnunca se arma con datos del usuario. Se genera del lado del servidor:transacciones/{transaction_id}/{tipo}-{uuid}.{ext}.- Validación de subida: tipo MIME real (no la extensión), máximo 10 MB, y solo
pdf/jpg/png— "los soportes pueden ser fotos, escaneos o pdfs".
3. Flujo del pago
El orden sale de la hoja Módulo caja (filas 5 a 14):
- El operador busca el MTCN en la pantalla Pago (ya existe, E2).
- Nuevo: si la transacción ya está pagada, se muestra igual con sus soportes — "corrobora soportes del pago para eliminar posibilidades de error". Hoy el buscador la descarta.
- Nuevo: verificación del PIN —que es el MTCN— antes de habilitar el pago. Sigue siendo un control real y no una formalidad: la pantalla Pago permite buscar por nombre, y en ese caso pedir el PIN es lo único que confirma que el cliente trae la carta correcta.
- Nuevo: completar los datos faltantes y elegir el medio de pago.
- Confirmar el pago (ya existe: descuento de caja + prefondeo, E4/E4b).
- Nuevo: subir los documentos. Si faltan obligatorios, la transacción queda pagada e incompleta, y se puede completar después desde su ficha.
El pago no se bloquea por documentos faltantes. Es lo que pide el cliente y además es lo correcto operativamente: la plata ya se entregó en el mostrador; impedir registrar el pago porque falta un escaneo dejaría la caja descuadrada contra la realidad física.
4. Permisos
Sin módulo nuevo: los documentos son parte de la transacción.
- Subir →
transacciones:writesobre el país (mismo gate que pagar). - Ver y descargar →
transacciones:read, con el alcance por PDV deTransactionController::authorizeScope— el mismo agujero que se corrigió en Caja el 06/08 no se repite acá. - Borrar → no existe. Un soporte contable no se borra.
5. Endpoints
POST /transacciones/{transaction}/documentos subir (multipart)
GET /transacciones/{transaction}/documentos listar (metadata, sin contenido)
GET /transacciones/{transaction}/documentos/{documento} descargar (audita)
PATCH /transacciones/{transaction}/datos-pago completar campos + medio de pago
POST /transacciones/{transaction}/verificar-pin validar el PIN6. Tests obligatorios
- Subir sin permiso → 403; con permiso de otro PDV → 403.
- Descargar audita y respeta el alcance por PDV.
- Un archivo que dice ser PDF pero no lo es → rechazado (MIME real, no extensión).
- Más de 10 MB → rechazado.
documentacion_completaesfalseconEFECTIVOsin papeleta, ytruecon los cuatro obligatorios; conTRANSFERENCIAexige los cuatro adicionales.- La selfie nunca afecta
documentacion_completa. - Pagar sin documentos funciona y deja la transacción
PAID+ incompleta. - PIN incorrecto no habilita el pago y queda auditado; el MTCN de otra transacción no sirve.
7. Fuera de alcance de E5a
- Papeleta y formulario de enrolamiento → E5b.
- Reporte regulatorio de 36 columnas → entrega propia; E5a le deja los datos que le faltaban.
- Género y estado civil, que pide el formulario de enrolamiento pero no el reporte → se definen en E5b, cuando se sepa si el formulario se genera o se sube escaneado.

