Appearance
Guía Completa de Integración de Perfiles
Esta guía cubre todos los flujos de trabajo relacionados con perfiles, formularios y validación de datos en Kuenta. Incluye ejemplos prácticos para:
- Creación de clientes (debtors)
- Actualización de perfiles
- Manejo de formularios con missingFields
- Solicitudes de crédito con validación dinámica
Conceptos Fundamentales
Arquitectura de Perfiles
Entidad (Entity)
├── Perfil Maestro (Master Profile)
│ ├── Natural (persona natural)
│ └── Legal (persona jurídica)
├── Perfiles Compartidos (Shared Profiles)
│ └── Por cada organización donde participa
└── Copias de Perfil (Profile Copies)
└── Snapshots inmutables para créditosIDs Importantes
| ID | Descripción | Uso |
|---|---|---|
entityID | ID de la entidad (persona/empresa) | Identificador principal |
profileID | ID del perfil específico | Para actualizaciones de perfil |
configOrgID | ID de la organización de marca blanca | Para cargar formularios |
debtorID | ID del deudor (alias de entityID) | En endpoints de deudores |
1. Creación de Clientes (Debtors)
Flujo Completo
1. Crear deudor (POST /debtor)
↓
2. Cargar formularios de la marca blanca
↓
3. Actualizar perfil con datos requeridos
↓
4. Perfil listo para solicitar créditosPaso 1: Crear el Deudor
Endpoint: POST /debtor
bash
curl -X POST "https://api.kuenta.co/v1/debtor" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Config-Organization-ID: $CONFIG_ORG_ID" \
-H "Organization-ID: $CREDITOR_ID" \
-d '{
"type": "natural",
"idType": "CC",
"idNumber": "1234567890"
}'Respuesta:
json
{
"data": {
"debtor": {
"id": "770e8400-e29b-41d4-a716-446655440002",
"type": "natural",
"idType": "CC",
"idNumber": "1234567890",
"status": "pending",
"parentID": "660e8400-e29b-41d4-a716-446655440001",
"profile": {
"id": "880e8400-e29b-41d4-a716-446655440003",
"name": "",
"email": "",
"phone": ""
}
}
},
"success": true
}Nota: El perfil se crea vacío. Debes completarlo con los campos requeridos por la marca blanca.
Paso 2: Cargar Formularios de la Marca Blanca
Endpoint: GET /entities/{configOrgID}/config/forms?type=natural
javascript
// Cargar formularios para persona natural
const response = await fetch(
`${API_URL}/entities/${configOrgID}/config/forms?type=natural`,
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'Config-Organization-ID': configOrgID,
'Organization-ID': creditorID
}
}
);
const { data: { form } } = await response.json();
// form.fields contiene todos los campos configuradosRespuesta:
json
{
"status": "success",
"data": {
"form": {
"ID": "form-uuid",
"name": "Formulario Combinado",
"fields": [
{
"ID": "formfield-uuid-1",
"fieldID": "field-uuid-1",
"actived": true,
"required": true,
"field": {
"name": "firstName",
"label": "Primer Nombre",
"type": "text"
}
},
{
"ID": "formfield-uuid-2",
"fieldID": "field-uuid-2",
"actived": true,
"required": true,
"field": {
"name": "lastName",
"label": "Apellido",
"type": "text"
}
},
{
"ID": "formfield-uuid-3",
"fieldID": "field-uuid-3",
"actived": true,
"required": false,
"field": {
"name": "incomeMonthly",
"label": "Ingreso Mensual",
"type": "currency"
}
}
]
}
}
}Paso 3: Actualizar el Perfil
Endpoint: PUT /debtor/{debtorID}/profile
javascript
const updateProfile = async (debtorID, profileData, missingFields) => {
const response = await fetch(
`${API_URL}/debtor/${debtorID}/profile`,
{
method: 'PUT',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'Config-Organization-ID': configOrgID,
'Organization-ID': creditorID
},
body: JSON.stringify({
natural: profileData,
missingFields: missingFields
})
}
);
return response.json();
};
// Ejemplo de uso
const profileData = {
firstName: "Juan",
lastName: "Pérez",
email: "[email protected]",
mobilePhone: "3001234567",
incomeMonthly: 3500000 // Campo numérico
};
// missingFields: campos que fueron "preguntados" en el formulario
const missingFields = form.fields.map(f => ({
fieldID: f.fieldID,
name: f.field.name
}));
await updateProfile(debtorID, profileData, missingFields);Payload completo:
json
{
"natural": {
"firstName": "Juan",
"lastName": "Pérez",
"email": "[email protected]",
"mobilePhone": "3001234567",
"incomeMonthly": 3500000,
"idType": 1,
"idNumber": "1234567890"
},
"missingFields": [
{ "fieldID": "field-uuid-1", "name": "firstName" },
{ "fieldID": "field-uuid-2", "name": "lastName" },
{ "fieldID": "field-uuid-3", "name": "incomeMonthly" }
]
}2. Actualización de Perfiles
Tipos de Actualización
| Tipo | Endpoint | Caso de Uso |
|---|---|---|
| Perfil propio | PUT /profile | Usuario actualiza su propio perfil |
| Perfil de deudor | PUT /debtor/{id}/profile | Acreedor actualiza perfil de deudor |
| Campo específico | PUT /debtor/{id}/profiles/{profileID}/field | Actualizar un solo campo |
| Perfil de organización | PUT /organization/{id}/profile/{profileID} | Admin actualiza miembro |
Actualización de Perfil Propio
Endpoint: PUT /profile
javascript
const updateOwnProfile = async (profileData) => {
// 1. Cargar formularios disponibles
const formsResponse = await fetch(
`${API_URL}/entities/${configOrgID}/config/forms?type=natural`,
{ headers: authHeaders }
);
const { data: { form } } = await formsResponse.json();
// 2. Construir missingFields de los campos que se actualizarán
const missingFields = form.fields
.filter(f => f.actived && f.field.name in profileData)
.map(f => ({ fieldID: f.fieldID, name: f.field.name }));
// 3. Enviar actualización
const response = await fetch(`${API_URL}/profile`, {
method: 'PUT',
headers: {
...authHeaders,
'Content-Type': 'application/json'
},
body: JSON.stringify({
natural: profileData,
missingFields: missingFields
})
});
return response.json();
};Actualización de Campo Específico
Cuando solo necesitas actualizar un campo:
Endpoint: PUT /debtor/{debtorID}/profiles/{profileID}/field
javascript
const updateSingleField = async (debtorID, profileID, fieldName, fieldValue) => {
const response = await fetch(
`${API_URL}/debtor/${debtorID}/profiles/${profileID}/field`,
{
method: 'PUT',
headers: {
...authHeaders,
'Content-Type': 'application/json'
},
body: JSON.stringify({
field: fieldName,
value: fieldValue
})
}
);
return response.json();
};
// Ejemplo: Actualizar solo el email
await updateSingleField(debtorID, profileID, 'email', '[email protected]');3. Solicitudes de Crédito con MissingFields
El flujo de solicitud de crédito puede requerir información adicional del perfil. El sistema utiliza missingFields para indicar qué datos faltan.
Flujo de Solicitud con Validación Dinámica
1. Crear solicitud de crédito (POST /receivables o POST /payables)
↓
2. Si faltan datos → Status: AwaitingForm (13)
↓
3. Recibir missingFields en la respuesta
↓
4. Mostrar formulario con SOLO los campos faltantes
↓
5. Enviar datos y reintentar
↓
6. Repetir hasta que no haya missingFieldsCrear Solicitud de Crédito
Endpoint: POST /receivables (perspectiva del acreedor)
javascript
const createCredit = async (creditData) => {
const response = await fetch(`${API_URL}/receivables`, {
method: 'POST',
headers: {
...authHeaders,
'Content-Type': 'application/json'
},
body: JSON.stringify(creditData)
});
const result = await response.json();
// Verificar si hay campos faltantes
if (result.data.credit.status === 13) { // AwaitingForm
return {
status: 'awaiting_form',
credit: result.data.credit,
missingFields: result.data.missingFields,
profile: result.data.profile
};
}
return {
status: 'success',
credit: result.data.credit
};
};
// Ejemplo de uso
const creditResult = await createCredit({
debtorID: "debtor-uuid",
creditLineID: "credit-line-uuid",
principal: 5000000,
time: 90,
rate: 0.025
});
if (creditResult.status === 'awaiting_form') {
// Mostrar formulario con missingFields
showMissingFieldsForm(creditResult.missingFields, creditResult.profile);
}Respuesta con MissingFields
Cuando el perfil está incompleto:
json
{
"data": {
"credit": {
"ID": "credit-uuid",
"status": 13,
"statusDescription": "Esperando información del formulario"
},
"missingFields": [
{
"ID": "formfield-uuid-1",
"fieldID": "field-uuid-1",
"formID": "form-uuid",
"actived": true,
"required": true,
"field": {
"name": "occupation",
"label": "Ocupación",
"type": "text"
}
},
{
"ID": "formfield-uuid-2",
"fieldID": "field-uuid-2",
"formID": "form-uuid",
"actived": true,
"required": true,
"field": {
"name": "incomeMonthly",
"label": "Ingreso Mensual",
"type": "currency"
}
}
],
"profile": {
"ID": "profile-uuid",
"firstName": "Juan",
"lastName": "Pérez"
}
}
}Completar Formulario con MissingFields
Importante: Solo mostrar y enviar los campos que están en missingFields.
javascript
const completeMissingFields = async (creditID, missingFields, formValues) => {
// 1. Filtrar solo los campos que fueron "preguntados"
const profileData = {};
const fieldsToSend = [];
for (const field of missingFields) {
const fieldName = field.field.name;
if (fieldName in formValues) {
profileData[fieldName] = formValues[fieldName];
fieldsToSend.push({
fieldID: field.fieldID,
name: fieldName
});
}
}
// 2. Actualizar perfil del deudor
const updateResponse = await fetch(
`${API_URL}/payables/${creditID}/updates/profile`,
{
method: 'PUT',
headers: {
...authHeaders,
'Content-Type': 'application/json'
},
body: JSON.stringify({
natural: profileData,
missingFields: fieldsToSend
})
}
);
return updateResponse.json();
};
// Ejemplo: Usuario completa los campos faltantes
const formValues = {
occupation: "Ingeniero de Software",
incomeMonthly: 8500000
};
const result = await completeMissingFields(creditID, missingFields, formValues);
// Verificar si hay más campos faltantes
if (result.data.credit.status === 13) {
// Aún faltan campos
showMissingFieldsForm(result.data.missingFields, result.data.profile);
} else {
// Proceso continúa
console.log('Perfil completo, crédito en análisis');
}Endpoint para Actualizar durante Solicitud
Perspectiva del deudor: PUT /payables/{creditID}/updates/{ask}
bash
curl -X PUT "https://api.kuenta.co/v1/payables/{creditID}/updates/profile" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"natural": {
"occupation": "Ingeniero de Software",
"incomeMonthly": 8500000,
"company": "Tech Corp"
},
"missingFields": [
{ "fieldID": "field-uuid-1", "name": "occupation" },
{ "fieldID": "field-uuid-2", "name": "incomeMonthly" },
{ "fieldID": "field-uuid-3", "name": "company" }
]
}'4. Manejo de Campos Numéricos
Los campos numéricos requieren manejo especial:
Frontend: Conversión antes de enviar
javascript
// Campos numéricos conocidos
const numericFields = [
'incomeMonthly', 'variableIncome', 'otherIncome', 'spouseIncome',
'expenses', 'otherExpenses', 'assets', 'liabilities', 'worth'
];
const prepareProfileData = (formValues, fields) => {
const result = { ...formValues };
// Convertir campos vacíos a 0 para campos numéricos
for (const fieldName of numericFields) {
if (fieldName in result) {
if (result[fieldName] === '' || result[fieldName] === null || result[fieldName] === undefined) {
result[fieldName] = 0;
}
}
}
// Eliminar campos vacíos no numéricos
for (const [key, value] of Object.entries(result)) {
if (value === '' || value === null || value === undefined) {
if (!numericFields.includes(key)) {
delete result[key];
}
}
}
return result;
};Backend: Limpieza automática
El backend convierte automáticamente strings vacíos a null:
json
// Enviado por frontend
{ "incomeMonthly": "", "spouseIncome": "" }
// Procesado por backend
{ "incomeMonthly": null, "spouseIncome": null }5. Flujos Completos por Caso de Uso
Caso A: Registro de nuevo cliente y solicitud de crédito
javascript
async function registerClientAndApplyCredit(clientData, creditData) {
// 1. Crear el deudor
const debtor = await createDebtor({
type: 'natural',
idType: clientData.idType,
idNumber: clientData.idNumber
});
// 2. Cargar formularios de la marca blanca
const forms = await loadForms(configOrgID, 'natural');
// 3. Actualizar perfil con datos iniciales
await updateDebtorProfile(debtor.id, clientData.profile, forms.fields);
// 4. Crear solicitud de crédito
let creditResult = await createCredit({
debtorID: debtor.id,
...creditData
});
// 5. Manejar campos faltantes si es necesario
while (creditResult.status === 'awaiting_form') {
// Mostrar formulario al usuario
const additionalData = await promptUserForFields(creditResult.missingFields);
// Enviar datos adicionales
creditResult = await completeMissingFields(
creditResult.credit.ID,
creditResult.missingFields,
additionalData
);
}
return creditResult;
}Caso B: Usuario edita su perfil desde cuenta
javascript
async function editOwnProfile(newData) {
// 1. Cargar formularios según profileEditScope de la organización
const forms = await loadForms(configOrgID, 'natural');
// El backend ya filtró según profileEditScope (all o base)
// 2. Mostrar formulario con campos disponibles
const formUI = buildForm(forms.fields);
// 3. Usuario modifica datos
const updatedValues = await getUserInput(formUI);
// 4. Preparar datos (convertir numéricos, limpiar vacíos)
const preparedData = prepareProfileData(updatedValues, forms.fields);
// 5. Construir missingFields solo con campos modificados
const modifiedFields = forms.fields.filter(f =>
f.field.name in preparedData
).map(f => ({
fieldID: f.fieldID,
name: f.field.name
}));
// 6. Enviar actualización
return await updateOwnProfile({
natural: preparedData,
missingFields: modifiedFields
});
}Caso C: Acreedor actualiza perfil de deudor
javascript
async function updateDebtorByCreditor(debtorID, updates) {
// 1. Obtener perfil actual del deudor
const currentProfile = await getDebtorProfile(debtorID);
// 2. Cargar formularios de la marca blanca
const forms = await loadForms(configOrgID, currentProfile.type);
// 3. Validar que los campos existen en el formulario
const validFields = forms.fields.filter(f =>
f.actived && f.field.name in updates
);
if (validFields.length === 0) {
throw new Error('No hay campos válidos para actualizar');
}
// 4. Preparar payload
const profileData = {};
const missingFields = [];
for (const field of validFields) {
const fieldName = field.field.name;
profileData[fieldName] = updates[fieldName];
missingFields.push({
fieldID: field.fieldID,
name: fieldName
});
}
// 5. Enviar actualización
return await fetch(`${API_URL}/debtor/${debtorID}/profile`, {
method: 'PUT',
headers: {
...authHeaders,
'Content-Type': 'application/json'
},
body: JSON.stringify({
natural: profileData,
missingFields: missingFields
})
});
}6. Control de ProfileEditScope
Configuración por Organización
El campo profileEditScope controla qué campos pueden editar los usuarios:
| Valor | Comportamiento |
|---|---|
all (defecto) | Todos los formularios combinados |
base | Solo el formulario básico de la marca blanca |
Verificar Configuración
javascript
const getProfileEditScope = async (configOrgID) => {
const response = await fetch(
`${API_URL}/organization/${configOrgID}/config`,
{ headers: authHeaders }
);
const { data } = await response.json();
return data.configuration.profileEditScope || 'all';
};Cargar Formularios según Scope
El endpoint /entities/{id}/config/forms respeta automáticamente profileEditScope:
javascript
// Si profileEditScope = 'all': retorna todos los formularios mergeados
// Si profileEditScope = 'base': retorna solo el formulario básico
const forms = await fetch(
`${API_URL}/entities/${configOrgID}/config/forms?type=natural`,
{ headers: authHeaders }
);Forzar Formulario Básico
Para ignorar profileEditScope y obtener solo el básico:
javascript
const basicFormOnly = await fetch(
`${API_URL}/entities/${configOrgID}/config/forms?type=natural&name=base`,
{ headers: authHeaders }
);7. Validación de Formularios
Validación en Frontend
javascript
const validateField = (field, value) => {
const errors = [];
// Requerido
if (field.required && (value === '' || value === null || value === undefined)) {
errors.push(`${field.field.label} es requerido`);
}
// Validación por tipo
switch (field.field.type) {
case 'email':
if (value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
errors.push('Email inválido');
}
break;
case 'phone':
if (value && !/^\+?[\d\s-]{7,15}$/.test(value)) {
errors.push('Teléfono inválido');
}
break;
case 'number':
case 'currency':
if (value !== '' && isNaN(Number(value))) {
errors.push('Debe ser un número válido');
}
break;
}
// Validación personalizada
if (field.field.validation) {
const { pattern, min, max, minLength, maxLength } = field.field.validation;
if (pattern && value && !new RegExp(pattern).test(value)) {
errors.push('Formato inválido');
}
if (min !== undefined && Number(value) < min) {
errors.push(`Valor mínimo: ${min}`);
}
if (max !== undefined && Number(value) > max) {
errors.push(`Valor máximo: ${max}`);
}
if (minLength && value && value.length < minLength) {
errors.push(`Mínimo ${minLength} caracteres`);
}
if (maxLength && value && value.length > maxLength) {
errors.push(`Máximo ${maxLength} caracteres`);
}
}
return errors;
};
const validateForm = (fields, values) => {
const allErrors = {};
let hasErrors = false;
for (const field of fields) {
if (!field.actived) continue;
const fieldName = field.field.name;
const value = values[fieldName];
const errors = validateField(field, value);
if (errors.length > 0) {
allErrors[fieldName] = errors;
hasErrors = true;
}
}
return { valid: !hasErrors, errors: allErrors };
};Códigos de Error Comunes
| Código | Descripción | Solución |
|---|---|---|
400 BindError | JSON malformado o tipos incorrectos | Verificar estructura del payload |
400 ValidationError | Valores no cumplen validación | Verificar reglas del campo |
409 Conflict | Email o ID duplicado | El valor ya existe |
422 BusinessRuleViolation | Regla de negocio violada | Revisar políticas |
8. Ejemplos Completos
Python: Cliente completo para gestión de perfiles
python
import requests
from typing import Dict, List, Optional
from dataclasses import dataclass
@dataclass
class KuentaClient:
base_url: str
access_token: str
config_org_id: str
organization_id: str
@property
def headers(self) -> Dict:
return {
'Authorization': f'Bearer {self.access_token}',
'Content-Type': 'application/json',
'Config-Organization-ID': self.config_org_id,
'Organization-ID': self.organization_id
}
def create_debtor(self, entity_type: str, id_type: str, id_number: str) -> Dict:
"""Crear nuevo deudor"""
response = requests.post(
f'{self.base_url}/debtor',
headers=self.headers,
json={
'type': entity_type,
'idType': id_type,
'idNumber': id_number
}
)
response.raise_for_status()
return response.json()['data']['debtor']
def get_forms(self, entity_type: str = 'natural') -> Dict:
"""Obtener formularios de la marca blanca"""
response = requests.get(
f'{self.base_url}/entities/{self.config_org_id}/config/forms',
headers=self.headers,
params={'type': entity_type}
)
response.raise_for_status()
return response.json()['data']['form']
def update_debtor_profile(
self,
debtor_id: str,
profile_data: Dict,
fields: List[Dict]
) -> Dict:
"""Actualizar perfil de deudor"""
# Construir missingFields
missing_fields = [
{'fieldID': f['fieldID'], 'name': f['field']['name']}
for f in fields
if f['actived'] and f['field']['name'] in profile_data
]
response = requests.put(
f'{self.base_url}/debtor/{debtor_id}/profile',
headers=self.headers,
json={
'natural': profile_data,
'missingFields': missing_fields
}
)
response.raise_for_status()
return response.json()
def create_credit(self, credit_data: Dict) -> Dict:
"""Crear solicitud de crédito"""
response = requests.post(
f'{self.base_url}/receivables',
headers=self.headers,
json=credit_data
)
response.raise_for_status()
return response.json()['data']
def complete_missing_fields(
self,
credit_id: str,
profile_data: Dict,
missing_fields: List[Dict]
) -> Dict:
"""Completar campos faltantes de una solicitud"""
fields_to_send = [
{'fieldID': f['fieldID'], 'name': f['field']['name']}
for f in missing_fields
if f['field']['name'] in profile_data
]
response = requests.put(
f'{self.base_url}/payables/{credit_id}/updates/profile',
headers=self.headers,
json={
'natural': profile_data,
'missingFields': fields_to_send
}
)
response.raise_for_status()
return response.json()['data']
# Ejemplo de uso
if __name__ == '__main__':
client = KuentaClient(
base_url='https://api.kuenta.co/v1',
access_token='your_token',
config_org_id='config-org-uuid',
organization_id='org-uuid'
)
# 1. Crear deudor
debtor = client.create_debtor('natural', 'CC', '1234567890')
print(f"Deudor creado: {debtor['id']}")
# 2. Cargar formularios
form = client.get_forms('natural')
print(f"Formulario cargado con {len(form['fields'])} campos")
# 3. Actualizar perfil
profile_data = {
'firstName': 'Juan',
'lastName': 'Pérez',
'email': '[email protected]',
'mobilePhone': '3001234567',
'incomeMonthly': 5000000
}
result = client.update_debtor_profile(debtor['id'], profile_data, form['fields'])
print("Perfil actualizado exitosamente")
# 4. Crear crédito
credit_result = client.create_credit({
'debtorID': debtor['id'],
'creditLineID': 'credit-line-uuid',
'principal': 10000000,
'time': 180,
'rate': 0.025
})
# 5. Manejar campos faltantes si es necesario
if credit_result['credit']['status'] == 13: # AwaitingForm
print("Faltan campos, completando...")
additional_data = {
'occupation': 'Ingeniero',
'company': 'Tech Corp'
}
client.complete_missing_fields(
credit_result['credit']['ID'],
additional_data,
credit_result['missingFields']
)JavaScript/TypeScript: Módulo de gestión de perfiles
typescript
interface FormField {
ID: string;
fieldID: string;
actived: boolean;
required: boolean;
field: {
name: string;
label: string;
type: string;
validation?: {
pattern?: string;
min?: number;
max?: number;
minLength?: number;
maxLength?: number;
};
};
}
interface ProfileData {
[key: string]: any;
}
class KuentaProfileManager {
constructor(
private baseUrl: string,
private accessToken: string,
private configOrgId: string,
private organizationId: string
) {}
private get headers(): HeadersInit {
return {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json',
'Config-Organization-ID': this.configOrgId,
'Organization-ID': this.organizationId
};
}
// Campos numéricos que deben convertirse de "" a 0
private numericFields = new Set([
'incomeMonthly', 'variableIncome', 'otherIncome', 'spouseIncome',
'expenses', 'otherExpenses', 'assets', 'liabilities', 'worth',
'mortgageCommercialValue', 'pledgeCommercialValue'
]);
/**
* Prepara datos del perfil para envío
* - Convierte campos numéricos vacíos a 0
* - Elimina campos de texto vacíos
*/
prepareProfileData(data: ProfileData): ProfileData {
const result: ProfileData = {};
for (const [key, value] of Object.entries(data)) {
if (this.numericFields.has(key)) {
// Campos numéricos: vacío → 0
result[key] = (value === '' || value === null || value === undefined)
? 0
: Number(value);
} else if (value !== '' && value !== null && value !== undefined) {
// Otros campos: solo incluir si tienen valor
result[key] = value;
}
}
return result;
}
/**
* Construye el array de missingFields para el payload
*/
buildMissingFields(fields: FormField[], data: ProfileData): Array<{fieldID: string, name: string}> {
return fields
.filter(f => f.actived && f.field.name in data)
.map(f => ({
fieldID: f.fieldID,
name: f.field.name
}));
}
/**
* Carga formularios de la marca blanca
*/
async loadForms(type: 'natural' | 'legal'): Promise<{fields: FormField[]}> {
const response = await fetch(
`${this.baseUrl}/entities/${this.configOrgId}/config/forms?type=${type}`,
{ headers: this.headers }
);
if (!response.ok) {
throw new Error(`Error loading forms: ${response.status}`);
}
const { data } = await response.json();
return data.form;
}
/**
* Crea un nuevo deudor
*/
async createDebtor(type: 'natural' | 'legal', idType: string, idNumber: string) {
const response = await fetch(`${this.baseUrl}/debtor`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify({ type, idType, idNumber })
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Error creating debtor');
}
const { data } = await response.json();
return data.debtor;
}
/**
* Actualiza el perfil de un deudor
*/
async updateDebtorProfile(
debtorId: string,
profileData: ProfileData,
fields: FormField[]
) {
const preparedData = this.prepareProfileData(profileData);
const missingFields = this.buildMissingFields(fields, preparedData);
const response = await fetch(`${this.baseUrl}/debtor/${debtorId}/profile`, {
method: 'PUT',
headers: this.headers,
body: JSON.stringify({
natural: preparedData,
missingFields
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Error updating profile');
}
return response.json();
}
/**
* Flujo completo: crear deudor y completar perfil
*/
async registerClient(
idType: string,
idNumber: string,
profileData: ProfileData,
type: 'natural' | 'legal' = 'natural'
) {
// 1. Crear deudor
const debtor = await this.createDebtor(type, idType, idNumber);
// 2. Cargar formularios
const form = await this.loadForms(type);
// 3. Actualizar perfil
await this.updateDebtorProfile(debtor.id, profileData, form.fields);
return debtor;
}
}
// Uso
const manager = new KuentaProfileManager(
'https://api.kuenta.co/v1',
'access_token',
'config-org-id',
'organization-id'
);
// Registrar cliente completo
const debtor = await manager.registerClient('CC', '1234567890', {
firstName: 'María',
lastName: 'González',
email: '[email protected]',
mobilePhone: '3109876543',
incomeMonthly: 4500000,
occupation: 'Contadora'
});
console.log('Cliente registrado:', debtor.id);9. Solución de Problemas
Error: "failed on 'numeric' tag"
Causa: Campo numérico recibió string vacío.
Solución:
javascript
// Antes de enviar, convertir vacíos a 0 o null
if (field.type === 'currency' || field.type === 'number') {
value = value === '' ? 0 : Number(value);
}Error: "Campo no encontrado"
Causa: El campo existe en el perfil pero no en el formulario de la marca blanca.
Solución: Verificar que el campo esté activado en los formularios:
javascript
const fieldExists = form.fields.some(f =>
f.actived && f.field.name === fieldName
);Error: "Perfil no sincronizado"
Causa: La sincronización entre perfiles maestro y compartido no ha completado.
Solución: Esperar unos segundos y reintentar, o usar el perfil maestro directamente.
MissingFields vacío pero crédito rechazado
Causa: Reglas de negocio (no de formulario) fallaron.
Verificar:
- Límites de la línea de crédito
- Scoring del cliente
- Políticas de la organización