200 - Success
Documento recibido correctamente
El XML fue validado exitosamente y encolado para procesamiento.
Acción: Guardar transactionID y proceder a consultar estado.
El método uploadDocument es el punto de entrada principal para enviar documentos de nómina electrónica al sistema Facturatech. Este endpoint:
transactionID para seguimiento| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
username | string | OK | Usuario de acceso al servicio |
password | string | OK | Contraseña encriptada en SHA256 |
xmlBase64 | string | OK | Documento XML de nómina codificado en Base64 |
<?xml version="1.0" encoding="UTF-8"?><x:Envelope xmlns:x="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:dem="urn:https://ws-nomina.facturatech.co/v1/demo/"> <x:Header/> <x:Body> <dem:FtechAction.uploadDocument x:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <username xsi:type="xsd:string">DATAEM19112025</username> <password xsi:type="xsd:string">f8c0e8471f126c77bd23f664f3ce251b8f9943f1d95a6ffdda72f8e6b76c9b93</password> <xmlBase64 xsi:type="xsd:string">PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPE5vbW...</xmlBase64> </dem:FtechAction.uploadDocument> </x:Body></x:Envelope><?php$wsdl = 'https://ws-nomina.facturatech.co/v1/demo/index.php?wsdl';
$client = new SoapClient($wsdl, [ 'trace' => 1, 'exceptions' => true, 'encoding' => 'UTF-8', 'soap_version' => SOAP_1_1]);
// Leer XML y codificar en Base64$xmlContent = file_get_contents('nomina_001.xml');$xmlBase64 = base64_encode($xmlContent);
// Parámetros$params = [ 'username' => 'DATAEM19112025', 'password' => 'f8c0e8471f126c77bd23f664f3ce251b8f9943f1d95a6ffdda72f8e6b76c9b93', 'xmlBase64' => $xmlBase64];
try { $response = $client->__soapCall('FtechAction.uploadDocument', [$params]);
echo "Código: " . $response->codigo . "\n"; echo "Transaction ID: " . $response->transaccionID . "\n"; echo "Mensaje: " . $response->mensaje . "\n";
} catch (SoapFault $e) { echo "Error: " . $e->getMessage() . "\n";}# Preparar el XML en Base64XML_BASE64=$(base64 -w 0 nomina_001.xml)
# Construir SOAP requestcurl -X POST https://ws-nomina.facturatech.co/v1/demo/index.php \ -H "Content-Type: text/xml; charset=utf-8" \ -H "SOAPAction: urn:https://ws-nomina.facturatech.co/v1/demo/#FtechAction.uploadDocument" \ -d @- << EOF<?xml version="1.0" encoding="UTF-8"?><x:Envelope xmlns:x="http://schemas.xmlsoap.org/soap/envelope/" xmlns:dem="urn:https://ws-nomina.facturatech.co/v1/demo/"> <x:Body> <dem:FtechAction.uploadDocument> <username>DATAEM19112025</username> <password>f8c0e8471f126c77bd23f664f3ce251b8f9943f1d95a6ffdda72f8e6b76c9b93</password> <xmlBase64>$XML_BASE64</xmlBase64> </dem:FtechAction.uploadDocument> </x:Body></x:Envelope>EOF<uploadDocumentResponse> <codigo>200</codigo> <transaccionID>340c8da2ee7c92713b43a9946319157d819cd2dd08d07329da622cd146cb39857e</transaccionID> <mensaje>Documento recibido correctamente</mensaje></uploadDocumentResponse>| Campo | Tipo | Descripción |
|---|---|---|
codigo | int | Código de estado HTTP |
transaccionID | string | Identificador único de la transacción (64 caracteres hexadecimales) |
mensaje | string | Descripción del resultado de la operación |
200 - Success
Documento recibido correctamente
El XML fue validado exitosamente y encolado para procesamiento.
Acción: Guardar transactionID y proceder a consultar estado.
400 - Bad Request
Error en la estructura del XML
Problemas de validación:
Acción: Revisar mensaje de error y corregir XML.
401 - Unauthorized
Error de autenticación
Credenciales inválidas:
Acción: Verificar credenciales en configuración.
500 - Internal Server Error
Error interno del servidor
Problema en el servicio Facturatech:
Acción: Reintentar después de 30-60 segundos.
Generación del XML
Dataemunáh genera el documento de nómina según estructura DIAN.
Validación Local (Recomendado)
Validar XML contra XSD antes de enviar para evitar errores 400.
Codificación Base64
$xmlBase64 = base64_encode($xmlContent);Envío a Facturatech
Ejecutar uploadDocument con credenciales y XML codificado.
Recepción de TransactionID
Si código = 200, almacenar transactionID en base de datos.
Registro en Log
Guardar request/response completo para auditoría.
Consulta de Estado
Proceder a ejecutar documentStatus con el transactionID.
Problema: Estructura XML no cumple con esquema DIAN.
{ "codigo": 400, "mensaje": "Error: Nodo 'Periodo' mandatorio faltante"}Solución:
Checklist de validación:
Problema: Credenciales incorrectas o expiradas.
{ "codigo": 401, "mensaje": "Error de autenticación: credenciales inválidas"}Solución:
// Verificar hash SHA256$password = "mi_password";$hash = hash('sha256', $password);echo $hash; // Debe coincidir con el configuradoProblema: Servicio no responde o tarda demasiado.
{ "codigo": 500, "mensaje": "Error interno del servidor"}Solución:
*.facturatech.cofunction enviarConReintentos($xml, $maxIntentos = 3) { $intento = 0; $delay = 30; // segundos
while ($intento < $maxIntentos) { try { return uploadDocument($xml); } catch (Exception $e) { $intento++; if ($intento >= $maxIntentos) { throw $e; } sleep($delay); $delay *= 2; // Backoff exponencial } }}Problema: Se excedió el límite de 20 peticiones/10 segundos.
Solución: Implementar sistema de cola.
class RateLimiter { private $maxRequests = 20; private $timeWindow = 10; // segundos private $requests = [];
public function allowRequest() { $now = time();
// Limpiar requests antiguos $this->requests = array_filter( $this->requests, fn($time) => ($now - $time) < $this->timeWindow );
if (count($this->requests) >= $this->maxRequests) { $waitTime = $this->timeWindow - ($now - min($this->requests)); sleep($waitTime); $this->requests = []; }
$this->requests[] = $now; return true; }}El transactionID debe almacenarse para operaciones posteriores:
<?phpfunction guardarTransactionID($nominaId, $transactionID, $xmlOriginal) { $db = Zend_Db_Table::getDefaultAdapter();
$data = [ 'nomina_id' => $nominaId, 'transaction_id' => $transactionID, 'estado' => 'ENVIADO', 'xml_original' => gzcompress($xmlOriginal), // Comprimir para ahorrar espacio 'fecha_envio' => new Zend_Db_Expr('NOW()'), 'intentos_envio' => 1 ];
$db->insert('nomina_electronica', $data);
// Log Zend_Log::info("TransactionID almacenado: $transactionID para nómina #$nominaId");}<?php$logData = [ 'timestamp' => date('Y-m-d H:i:s'), 'operacion' => 'uploadDocument', 'nomina_id' => $nominaId, 'empleado_id' => $empleadoId, 'prefijo' => $prefijo, 'consecutivo' => $consecutivo, 'transaction_id' => $response->transaccionID ?? null, 'codigo_respuesta' => $response->codigo, 'mensaje' => $response->mensaje, 'tiempo_respuesta' => $tiempoTranscurrido, 'tamano_xml' => strlen($xmlContent), 'ip_servidor' => $_SERVER['SERVER_ADDR'] ?? null];
Zend_Log::info('uploadDocument ejecutado', $logData);Antes de usar este endpoint en producción:
<?phpclass Nomina_Service_UploadDocument{ private $soapClient; private $rateLimiter; private $logger;
public function __construct() { $this->soapClient = new Dataemunah_Facturatech_SoapClient(); $this->rateLimiter = new Dataemunah_Facturatech_RateLimiter(); $this->logger = Zend_Registry::get('logger'); }
public function enviarNomina($nominaId) { try { // 1. Obtener datos de nómina $nomina = $this->obtenerNomina($nominaId);
// 2. Generar XML $xml = $this->generarXML($nomina);
// 3. Validar XML localmente if (!$this->validarXML($xml)) { throw new Exception('XML inválido según esquema XSD'); }
// 4. Codificar Base64 $xmlBase64 = base64_encode($xml);
// 5. Respetar rate limit $this->rateLimiter->allowRequest();
// 6. Enviar a Facturatech $startTime = microtime(true); $response = $this->soapClient->uploadDocument($xmlBase64); $elapsedTime = microtime(true) - $startTime;
// 7. Procesar respuesta if ($response['success'] && $response['codigo'] == 200) { $this->guardarTransactionID( $nominaId, $response['transaccionID'], $xml );
$this->logger->info("Nómina #$nominaId enviada exitosamente", [ 'transaction_id' => $response['transaccionID'], 'tiempo' => $elapsedTime ]);
return $response['transaccionID']; } else { throw new Exception($response['mensaje']); }
} catch (Exception $e) { $this->logger->error("Error al enviar nómina #$nominaId: " . $e->getMessage()); $this->marcarComoError($nominaId, $e->getMessage()); throw $e; } }
// Métodos auxiliares...}Después de ejecutar uploadDocument exitosamente:
Consultar el estado del documento con documentStatus usando el transactionID obtenido.