Skip to content

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.

ColumnaTipoOrigenNota
pinNo 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_attimestamp nullableSoterex / derivadoLa carta indica 60 días para retirar
nationalitystring nullableSoterexDistinto de destination_country
beneficiary_addressstring nullableCIS al pagar
beneficiary_citystring nullableCIS al pagar
beneficiary_statestring nullableCIS al pagar
identification_typestring nullableCIS al pagar
identification_countrychar(2) nullableCIS al pagar
identification_numberstring nullableCIS al pagar
phone_2string nullableCIS al pagar"SOTEREX ENVÍA UN TELÉFONO, AGREGAR UN TELÉFONO 2"
email_2string nullableCIS al pagar"SOTEREX ENVÍA UN MAIL, AGREGAR UN MAIL 2"
delivery_typestring nullableCIS al pagarEFECTIVO | TRANSFERENCIA
bank_accountstring nullableCIS al pagarSolo con TRANSFERENCIA
notestext nullableCIS al pagar"gestiones de contacto adicional"

Lo que NO se agrega: Payout Currency, Ex. Rate y Beneficiary Destination Amount del 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         timestamp

Catálogo de tipos (constantes en el modelo, mismo criterio que los motivos de caja):

TipoObligatorioAplica a
CARTATodos
IDENTIFICACION_ANVERSOTodos
IDENTIFICACION_REVERSOTodos
SELFIENo"DESEABLE, no es obligatorio"Todos
PAPELETA_FIRMADATodos
FORMULARIO_ENROLAMIENTOSolo TRANSFERENCIA
CARTOLA_BANCARIASolo TRANSFERENCIA
SOLICITUD_TRANSFERENCIASolo TRANSFERENCIA
CONFIRMACION_ACREDITACIONSolo 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: local en 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).
  • path nunca 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):

  1. El operador busca el MTCN en la pantalla Pago (ya existe, E2).
  2. 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.
  3. 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.
  4. Nuevo: completar los datos faltantes y elegir el medio de pago.
  5. Confirmar el pago (ya existe: descuento de caja + prefondeo, E4/E4b).
  6. 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:write sobre el país (mismo gate que pagar).
  • Ver y descargar → transacciones:read, con el alcance por PDV de TransactionController::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 PIN

6. 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_completa es false con EFECTIVO sin papeleta, y true con los cuatro obligatorios; con TRANSFERENCIA exige 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.

Documentación viva — se actualiza junto con el código, no es un anexo aparte.