Skip to content

Instantly share code, notes, and snippets.

@jluisflo
Created January 20, 2026 03:52
Show Gist options
  • Select an option

  • Save jluisflo/792d963e0f0db36a5b5f78554208f4aa to your computer and use it in GitHub Desktop.

Select an option

Save jluisflo/792d963e0f0db36a5b5f78554208f4aa to your computer and use it in GitHub Desktop.
MPAY: Design Doc - Exposición de Estados de Apelación en API de Productos

Design Doc: Exposición de Estados de Apelación en API de Productos

Fecha: 2026-01-19 Esfuerzo: 15-30 minutos Tipo: Quick Win


1. Problema

La app N1co necesita mostrar mensajes específicos según el estado de la solicitud de crédito del cliente:

  • "Necesitamos que subas tu AFP" (apelación pendiente)
  • "Tu solicitud está en revisión" (apelación completada)
  • "Tu solicitud fue rechazada" (rechazado manualmente)

Actualmente solo recibimos el código burofax_request_appeal sin detalles del estado.


2. Solución

Extender el objeto error_data en GET /api/v2/products para incluir el estado detallado de EquifaxRequest.

¿Por qué este enfoque?

El código actual ya consulta EquifaxRequest y ya retorna códigos de apelación. Solo agregamos más campos al objeto que ya se está usando.

Archivo: api-laravel/app/Services/Product/ProductService.php


3. Cambios de Código

Cambio #1: Líneas 61-67

Antes:

if (blank($assignedAmount->approved_amount) && filled($equifaxRequest)) {
    $error = true;
    $errorData = [
        'code' => ResponseCode::BUROFAX_REQUEST_APPEAL,
        'appeal_url' => get_setting('appeal_url')
    ];
}

Después:

if (blank($assignedAmount->approved_amount) && filled($equifaxRequest)) {
    $error = true;
    $errorData = [
        'code' => ResponseCode::BUROFAX_REQUEST_APPEAL,
        'appeal_url' => get_setting('appeal_url'),
        'application_status' => [
            'step' => $equifaxRequest->step,
            'final_decision' => $equifaxRequest->final_decision,
            'rejection_reason' => $equifaxRequest->rejection_reason,
            'appeal_needed' => $equifaxRequest->appeal_needed,
            'appeal_completed' => $equifaxRequest->appeal_completed,
        ]
    ];
}

Cambio #2: Líneas 142-146

Antes:

if ($equifaxRequest->final_decision === 'denied') {
    $errorData = [
        'code' => ResponseCode::BUROFAX_REQUEST_APPEAL,
        'appeal_url' => get_setting('appeal_url')
    ];
}

Después:

if ($equifaxRequest->final_decision === 'denied') {
    $errorData = [
        'code' => ResponseCode::BUROFAX_REQUEST_APPEAL,
        'appeal_url' => get_setting('appeal_url'),
        'application_status' => [
            'step' => $equifaxRequest->step,
            'final_decision' => $equifaxRequest->final_decision,
            'rejection_reason' => $equifaxRequest->rejection_reason,
            'appeal_needed' => $equifaxRequest->appeal_needed,
            'appeal_completed' => $equifaxRequest->appeal_completed,
        ]
    ];
}

4. Ejemplos de Response

Caso 1: Apelación pendiente (cliente debe subir AFP)

{
  "error": true,
  "error_data": {
    "code": "burofax_request_appeal",
    "appeal_url": "https://onboarding.mpay.com?uuid=...",
    "application_status": {
      "step": "appeal",
      "final_decision": "denied",
      "rejection_reason": "customer_not_found",
      "appeal_needed": true,
      "appeal_completed": false
    }
  },
  "data": [...]
}

Mensaje en UI: "Para continuar, necesitamos que subas tu constancia AFP"


Caso 2: Apelación completada (en revisión manual)

{
  "error": true,
  "error_data": {
    "code": "burofax_request_appeal",
    "appeal_url": "https://onboarding.mpay.com?uuid=...",
    "application_status": {
      "step": "appeal",
      "final_decision": "denied",
      "rejection_reason": "customer_not_found",
      "appeal_needed": true,
      "appeal_completed": true
    }
  },
  "data": [...]
}

Mensaje en UI: "Tu solicitud está siendo revisada. Te notificaremos pronto."


Caso 3: Rechazado manualmente

{
  "error": true,
  "error_data": {
    "code": "burofax_request_appeal",
    "appeal_url": "https://onboarding.mpay.com?uuid=...",
    "application_status": {
      "step": "evaluation",
      "final_decision": "rejected",
      "rejection_reason": "manual_rejection",
      "appeal_needed": false,
      "appeal_completed": false
    }
  },
  "data": [...]
}

Mensaje en UI: "Tu solicitud no fue aprobada. Contáctanos para más información."


5. Campos del Estado

Campo Tipo Valores Posibles Descripción
step string "appeal", "evaluation", "search", etc. Paso actual del proceso
final_decision string "approved", "denied", "rejected", null Decisión final
rejection_reason string Ver tabla abajo Razón del rechazo
appeal_needed boolean true/false Requiere apelación
appeal_completed boolean true/false Cliente ya subió AFP

Razones de Rechazo

Código Significado
customer_not_found No encontrado en buró de crédito
minimum_age No cumple edad mínima
maximum_age Excede edad máxima
minimum_salary Ingreso insuficiente
minimum_score Score crediticio bajo
invalid_bureau_rating Calificación bancaria inválida
manual_rejection Rechazado por agente de riesgo

6. Integración en N1co

Actualizar DTO

public class ProductErrorInfo
{
    public string Code { get; set; }
    public string AppealUrl { get; set; }

    [JsonProperty("application_status")]
    public CreditApplicationStatus ApplicationStatus { get; set; }
}

public class CreditApplicationStatus
{
    public string Step { get; set; }
    public string FinalDecision { get; set; }
    public string RejectionReason { get; set; }
    public bool AppealNeeded { get; set; }
    public bool AppealCompleted { get; set; }
}

Mapeo de Mensajes

public static string GetKycMessage(ProductErrorInfo errorInfo)
{
    if (errorInfo.Code != "burofax_request_appeal")
        return null;

    var status = errorInfo.ApplicationStatus;
    if (status == null)
        return "Procesando tu solicitud...";

    // Cliente debe subir AFP
    if (status.AppealNeeded && !status.AppealCompleted)
        return "Para continuar, necesitamos que subas tu constancia AFP";

    // En revisión manual
    if (status.AppealNeeded && status.AppealCompleted)
        return "Tu solicitud está siendo revisada. Te notificaremos pronto.";

    // Rechazado manualmente
    if (status.FinalDecision == "rejected")
        return "Tu solicitud no fue aprobada. Contáctanos para más información.";

    // Rechazado automáticamente
    if (status.FinalDecision == "denied")
        return GetAutoRejectionMessage(status.RejectionReason);

    return "Procesando tu solicitud de crédito...";
}

private static string GetAutoRejectionMessage(string reason)
{
    return reason switch
    {
        "customer_not_found" => "No encontramos tu registro en el buró de crédito",
        "minimum_age" => "No cumples con la edad mínima requerida",
        "minimum_salary" => "Tu ingreso mensual no cumple con el mínimo",
        "minimum_score" => "Tu perfil crediticio no cumple los requisitos",
        _ => "Tu solicitud no fue aprobada en este momento"
    };
}

7. Testing

Casos de Prueba

Escenario Verificar
Cliente con crédito aprobado error_data = null
No encontrado en buró appeal_needed=true, appeal_completed=false
Cliente subió AFP appeal_needed=true, appeal_completed=true
Rechazado por agente final_decision="rejected", rejection_reason="manual_rejection"
Rechazado por edad final_decision="denied", rejection_reason="minimum_age"

Script de Prueba

# Test con cliente que necesita apelación
curl -X GET "http://localhost/api/v2/products?customer={UUID}&amount=500" \
  -H "Authorization: Bearer {TOKEN}" | jq '.error_data.application_status'

# Verificar que retorne:
{
  "step": "appeal",
  "final_decision": "denied",
  "rejection_reason": "customer_not_found",
  "appeal_needed": true,
  "appeal_completed": false
}

8. Backward Compatibility

Cambio seguro:

  • Campos nuevos son opcionales
  • N1co puede ignorar application_status si no lo necesita
  • Código actual sigue funcionando sin cambios

9. Checklist de Implementación

MPAY (30 min):

  • Modificar ProductService.php líneas 61-67
  • Modificar ProductService.php líneas 142-146
  • Probar localmente con Postman
  • Commit: feat: add application_status to products error_data

N1co (1-2 horas):

  • Actualizar ProductErrorInfo y CreditApplicationStatus DTOs
  • Implementar GetKycMessage() helper
  • Testing end-to-end
  • Deploy a staging

10. Referencia Rápida

Estados Principales

Condición Estado en UI
appeal_needed=true + appeal_completed=false "Sube tu AFP"
appeal_needed=true + appeal_completed=true "En revisión"
final_decision="rejected" "Rechazado"
final_decision="denied" Ver razón específica
final_decision="approved" Crédito disponible

Ubicación de Archivos

  • MPAY: api-laravel/app/Services/Product/ProductService.php
  • Modelo: api-laravel/app/Models/EquifaxRequest.php
  • Enums: api-laravel/app/Enums/Equifax/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment