Detección de Clientes Extranjeros en QuickBooks - Documentación Técnica
Resumen Ejecutivo
Este documento explica el flujo completo de detección y manejo de clientes extranjeros en la integración con QuickBooks Online, desde la recepción del JSON hasta la aplicación de validaciones DGI en los componentes de facturación.
Arquitectura del Sistema
1. Flujo de Datos Completo
QuickBooks JSON → QuickBooksOnlineService → CustomersImp Model → Componentes Livewire
2. Mapping de Campos
| QuickBooks CustomerRef | Función QuickBooksOnlineService | CustomersImp Campo | Descripción |
|---|---|---|---|
TIPO_RECEPTOR |
array_get($customerRef, 'TIPO_RECEPTOR', '02') |
Custom_field3 |
Tipo de receptor DGI |
PASAPORTE |
array_get($customerRef, 'PASAPORTE') |
Custom_field1 (si extranjero) |
Pasaporte para tipo 04 |
RUC |
array_get($customerRef, 'RUC') |
Custom_field1 (si nacional) |
RUC para tipos 01-03 |
DV |
array_get($customerRef, 'DV') |
Custom_field2 |
Dígito verificador |
TIPO |
array_get($customerRef, 'TIPO') |
Custom_field4 |
Tipo contribuyente |
Código Fuente Detallado
QuickBooksOnlineService.php
Función: findOrCreateCustomerFromQuickBooks()
Ubicación: Líneas 738-750
$customer = $this->createDefaultClient($organization, [
'CustomerID' => $customerId,
'Customer_Bill_Name' => trim($customerName),
'Ruc' => array_get($customerRef, 'RUC'),
'Dv' => array_get($customerRef, 'DV'),
'TIPO' => array_get($customerRef, 'TIPO'),
'TIPO_RECEPTOR' => array_get($customerRef, 'TIPO_RECEPTOR', '02'),
'ReceiverType' => array_get($customerRef, 'ReceiverType', '2'),
'LocationCode' => array_get($customerRef, 'BillAddr.PostalCode', null),
'Email' => array_get($customerRef, 'PrimaryEmail', $email),
'AddressLine1' => array_get($customerRef, 'BillAddr.Line1', ''),
'AddressLine2' => array_get($customerRef, 'BillAddr.Line2', ''),
'Country' => $correctedCountry, // País corregido para operaciones internas
]);
Función: createDefaultClient()
Ubicación: Líneas 466-610
Lógica de Detección de Extranjeros:
// Determinar TIPO_RECEPTOR independientemente del tipoContribuyente
$tipoReceptor = '02'; // Default: Consumidor final
// Verificar si viene TIPO_RECEPTOR directamente en el request
$tipoReceptorFromRequest = array_get($request, 'TIPO_RECEPTOR');
if (!is_null($tipoReceptorFromRequest)) {
// Normalizar formato: asegurar que tenga cero al inicio
$tipoReceptorNormalized = str_pad((string)$tipoReceptorFromRequest, 2, '0', STR_PAD_LEFT);
// Validar que sea un valor permitido
if (in_array($tipoReceptorNormalized, ['01', '02', '03', '04'])) {
$tipoReceptor = $tipoReceptorNormalized;
}
} else {
// Lógica automática basada en criterios específicos:
if (!empty($pasaporteFromRequest)) {
$tipoReceptor = '04'; // Extranjero (tiene PASAPORTE)
$pasaporte = $pasaporteFromRequest;
} elseif (!empty($ruc) && !empty($dv)) {
$tipoReceptor = '01'; // Contribuyente (tiene RUC)
}
}
// Lógica de validación cruzada para extranjeros (TIPO_RECEPTOR: 04)
if ($tipoReceptor === '04') {
// Para extranjeros: PASAPORTE debe estar presente, RUC/DV/TIPO deben ser null
if (!empty($pasaporteFromRequest)) {
$pasaporte = $pasaporteFromRequest;
// Limpiar campos que no aplican para extranjeros
$ruc = null;
$dv = null;
$tipoFromRequest = null;
} else {
// Si es extranjero pero no tiene pasaporte, cambiar a consumidor final
$tipoReceptor = '02';
}
} else {
// Para no extranjeros: PASAPORTE debe ser null
$pasaporte = null;
}
Mapping a CustomersImp:
$customer = $modelCustomer->updateOrCreate([
// ... campos de identificación
], [
// Campos personalizados para facturación electrónica Panama
'Custom_field1' => $tipoReceptor === '04' ? $pasaporte : $ruc, // RUC para nacionales, PASAPORTE para extranjeros
'Custom_field2' => $tipoReceptor === '04' ? null : $dv, // DV solo para nacionales, null para extranjeros
'Custom_field3' => $tipoReceptor, // TIPO_RECEPTOR: 01=Contribuyente, 02=Consumidor final, 03=Gobierno, 04=Extranjero
'Custom_field4' => $tipoContribuyente, // Tipo de contribuyente: 1=Persona Natural, 2=Contribuyente
'Custom_field5' => $locationCode, // Código de provincia/distrito
]);
Detección en Componentes Livewire
Patrón Implementado en Todos los Componentes
Archivos: CreateFastJob.php, CreateFast.php, Create.php, FE/Create.php
// Detectar cliente extranjero desde Custom_field3 (TIPO_RECEPTOR)
$tipoReceptor = $customer->Custom_field3 ?? '02';
if ($tipoReceptor === '04') {
// ✅ CLIENTE EXTRANJERO DETECTADO
Log::info('Cliente extranjero detectado', [
'customer_id' => $customer->CustomerID,
'customer_name' => $customer->Customer_Bill_Name,
'tipo_receptor' => $tipoReceptor,
'pasaporte' => $customer->Custom_field1
]);
// Configurar nacionalidad basado en Country del cliente
$countryCode = $customer->Country ?? 'US'; // Default a US si no está definido
$paisDestino = \App\Models\Destinationcountryoperation::query()
->where('code', $countryCode)
->orWhere('name', 'like', '%' . $countryCode . '%')
->first();
if ($paisDestino) {
// CORRECCIÓN DGI: Para extranjeros
// - Nacionalidad del Receptor = País del cliente (ej: Chile)
// - País Destino de la Operación = Panamá (donde se emite la factura)
// - Destino de la Operación = 1 (Panamá - porque la operación es en Panamá)
$this->receptor_paisNacionalidad = $paisDestino->id; // Nacionalidad: país del cliente
// Buscar Panamá para destino de operación
$panama = \App\Models\Destinationcountryoperation::query()
->where('code', 'PA')
->orWhere('name', 'like', '%Panamá%')
->orWhere('name', 'like', '%Panama%')
->first();
if ($panama) {
$this->receptor_paisDestinoOperacion = $panama->id; // Destino: Panamá
$this->destinoOperacion = 1; // Panamá (la operación se realiza en Panamá)
}
Log::info('País configurado para extranjero CORREGIDO', [
'nacionalidad_pais_id' => $this->receptor_paisNacionalidad,
'nacionalidad_pais_name' => $paisDestino->name,
'destino_operacion_pais_id' => $this->receptor_paisDestinoOperacion,
'destino_operacion_pais_name' => $panama->name ?? 'No encontrado',
'destinoOperacion' => $this->destinoOperacion
]);
}
}
Validaciones DGI
Criterios de Validación PAC
Error Original: "El país del cliente debe ser PA si el destino de la operación es 1= Panamá"
Solución Implementada:
1. Detección Automática: Usar Custom_field3 para identificar TIPO_RECEPTOR = '04'
2. Configuración de País: Asignar país basado en campo Country del cliente
3. Destino de Operación: Para extranjeros, siempre usar destinoOperacion = 2
Tipos de Receptor DGI
| Código | Descripción | Validaciones |
|---|---|---|
01 |
Contribuyente | Requiere RUC/DV válidos |
02 |
Consumidor Final | Default para clientes sin RUC |
03 |
Gobierno | Entidades gubernamentales |
04 |
Extranjero | Requiere PASAPORTE, país != PA |
Casos de Uso
Caso 1: Cliente Chileno (Solmary)
JSON QuickBooks:
{
"CustomerRef": {
"TIPO_RECEPTOR": "04",
"PASAPORTE": "12345678",
"Country": "CL"
}
}
Procesamiento:
1. QuickBooksOnlineService: Detecta TIPO_RECEPTOR = "04"
2. CustomersImp: Almacena en Custom_field3 = "04"
3. CreateFastJob: Lee Custom_field3, detecta extranjero
4. Resultado CORREGIDO:
- receptor_paisNacionalidad = Chile
- receptor_paisDestinoOperacion = Panamá
- destinoOperacion = 1 (Panamá)
Caso 2: Cliente Panameño Contribuyente
JSON QuickBooks:
{
"CustomerRef": {
"TIPO_RECEPTOR": "01",
"RUC": "8-1234-567",
"DV": "12",
"Country": "PA"
}
}
Procesamiento:
1. QuickBooksOnlineService: Detecta TIPO_RECEPTOR = "01"
2. CustomersImp: Almacena en Custom_field3 = "01"
3. CreateFastJob: Lee Custom_field3, detecta nacional
4. Resultado:
- receptor_paisNacionalidad = Panamá
- receptor_paisDestinoOperacion = Panamá
- destinoOperacion = 1 (Panamá)
Debugging y Troubleshooting
Logs Importantes
// En QuickBooksOnlineService.createDefaultClient()
Log::info('QuickBooksOnlineService.createDefaultClient: DEBUGGING Country Assignment', [
'customer_name' => array_get($request, 'Customer_Bill_Name', ''),
'tipo_receptor' => $tipoReceptor,
'country_from_request' => $countryFromRequest,
'default_country' => $defaultCountry,
'final_country' => $finalCountry,
'all_request_data' => $request
]);
// En CreateFastJob (y otros componentes)
Log::info('Cliente extranjero detectado', [
'customer_id' => $customer->CustomerID,
'customer_name' => $customer->Customer_Bill_Name,
'tipo_receptor' => $tipoReceptor,
'pasaporte' => $customer->Custom_field1
]);
Verificación de Datos
SQL para verificar datos del cliente:
SELECT
CustomerID,
Customer_Bill_Name,
Country,
Custom_field1 AS 'RUC_o_PASAPORTE',
Custom_field2 AS 'DV',
Custom_field3 AS 'TIPO_RECEPTOR',
Custom_field4 AS 'TIPO_CONTRIBUYENTE',
Custom_field5 AS 'LOCATION_CODE'
FROM CustomersImp
WHERE Customer_Bill_Name LIKE '%Solmary%';
Estado Actual del Sistema
✅ Componentes Corregidos
- CreateFast.php: Detección automática implementada
- Create.php: Detección automática implementada
- FE/Create.php: Detección automática implementada
- CreateFastJob.php: Detección automática implementada
✅ Commit Aplicado
Hash: af454e9d
Mensaje: feat: implementar detección automática de clientes extranjeros en componentes de facturación
📋 Validaciones Pendientes
- [ ] Testing con datos reales de QuickBooks
- [ ] Verificación de países no estándar (códigos personalizados)
- [ ] Manejo de casos edge (clientes sin país definido)
Conclusiones
El sistema ahora maneja correctamente:
- Recepción de Datos: QuickBooks envía TIPO_RECEPTOR y PASAPORTE en CustomerRef
- Almacenamiento: Se guarda en CustomersImp.Custom_field3 (TIPO_RECEPTOR) y Custom_field1 (PASAPORTE)
- Detección: Todos los componentes leen Custom_field3 para identificar extranjeros
- Configuración CORREGIDA: Para extranjeros se asigna nacionalidad del cliente + destino Panamá + destinoOperacion = 1
- Validación DGI: Se cumple con "todas las operaciones son en Panamá (destino = 1)"
🔄 CORRECCIÓN CRÍTICA APLICADA
ANTES (Incorrecto):
- Extranjeros: destinoOperacion = 2 + país destino = país del cliente
- Validación DGI fallaba para ciertos casos
DESPUÉS (Correcto):
- TODAS las operaciones: destinoOperacion = 1 (Panamá)
- Extranjeros: Nacionalidad = país del cliente, Destino = Panamá
- Nacionales: Nacionalidad = Panamá, Destino = Panamá
- Validación DGI: "todas las operaciones son en territorio panameño"
El error original "El país del cliente debe ser PA si el destino de la operación es 1= Panamá" se resuelve porque ahora el sistema correctamente interpreta que todas las operaciones ocurren en Panamá, pero distingue la nacionalidad del receptor.