200 - Autorizado
Documento firmado y autorizado por la DIAN
El mensaje incluye el número del documento:
"La nómina [PREFIJO][FOLIO] ha sido autorizada"
Acción: Proceder a descargar XML/PDF/CUNE
El método documentStatus permite consultar el estado de procesamiento de un documento previamente enviado con uploadDocument. Este endpoint es crucial porque:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
username | string | OK | Usuario de acceso al servicio |
password | string | OK | Contraseña encriptada en SHA256 |
transaccionID | string | OK | ID retornado por uploadDocument (64 caracteres hex) |
<?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.documentStatus x:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <username xsi:type="xsd:string">DATAEM19112025</username> <password xsi:type="xsd:string">f8c0e8471f126c77bd23f664f3ce251b8f9943f1d95a6ffdda72f8e6b76c9b93</password> <transaccionID xsi:type="xsd:string">340c8da2ee7c92713b43a9946319157d819cd2dd08d07329da622cd146cb39857e</transaccionID> </dem:FtechAction.documentStatus> </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]);
$params = [ 'username' => 'DATAEM19112025', 'password' => 'f8c0e8471f126c77bd23f664f3ce251b8f9943f1d95a6ffdda72f8e6b76c9b93', 'transaccionID' => '340c8da2ee7c92713b43a9946319157d819cd2dd08d07329da622cd146cb39857e'];
try { $response = $client->__soapCall('FtechAction.documentStatus', [$params]);
echo "Código: " . $response->codigo . "\n"; echo "Mensaje: " . $response->mensaje . "\n";
if ($response->codigo == 200) { echo " Documento AUTORIZADO\n"; } else { echo " Documento en proceso...\n"; }
} catch (SoapFault $e) { echo "Error: " . $e->getMessage() . "\n";}// Obtener transactionID de variable de entornoconst transactionID = pm.environment.get("transactionID");
if (!transactionID) { throw new Error("transactionID no encontrado. Ejecutar uploadDocument primero.");}
console.log("Consultando estado de:", transactionID);<x:Envelope xmlns:x="http://schemas.xmlsoap.org/soap/envelope/" xmlns:dem="urn:https://ws-nomina.facturatech.co/v1/demo/"> <x:Body> <dem:FtechAction.documentStatus> <username>DATAEM19112025</username> <password>f8c0e8471f126c77bd23f664f3ce251b8f9943f1d95a6ffdda72f8e6b76c9b93</password> <transaccionID>{{transactionID}}</transaccionID> </dem:FtechAction.documentStatus> </x:Body></x:Envelope><documentStatusResponse> <codigo>200</codigo> <mensaje>La nómina NOM35921 ha sido autorizada</mensaje> <mensajeError></mensajeError></documentStatusResponse><documentStatusResponse> <codigo>202</codigo> <mensaje>Documento en proceso de firma</mensaje> <mensajeError></mensajeError></documentStatusResponse><documentStatusResponse> <codigo>400</codigo> <mensaje>Error en validación DIAN</mensaje> <mensajeError>Nodo 'DevengadosTotal' no cuadra con suma de devengados</mensajeError></documentStatusResponse>200 - Autorizado
Documento firmado y autorizado por la DIAN
El mensaje incluye el número del documento:
"La nómina [PREFIJO][FOLIO] ha sido autorizada"
Acción: Proceder a descargar XML/PDF/CUNE
202 - En Proceso
Documento aún en cola de procesamiento
El sistema está validando y firmando el documento.
Acción: Continuar polling cada 3-5 segundos
400 - Error de Validación
Error detectado en el documento
La DIAN rechazó el documento por:
Acción: Revisar mensajeError y corregir documento
404 - No Encontrado
TransactionID inválido o inexistente
Causas posibles:
Acción: Verificar transactionID almacenado
Espera Inicial
Esperar 2 segundos después de uploadDocument antes del primer intento.
Polling con Intervalos
Consultar cada 2 segundos durante los primeros 20 segundos.
Backoff Progresivo
Después de 20s, aumentar intervalo a 3 segundos.
Timeout Final
Máximo 4 minutos de espera antes de marcar como error.
<?phpclass Nomina_Service_DocumentStatusPoller{ private $soapClient; private $maxAttempts = 120; // 4 minutos máximo private $initialDelay = 2; // segundos private $pollingInterval = 3; // segundos
public function __construct() { $this->soapClient = new Dataemunah_Facturatech_SoapClient(); }
/** * Consulta el estado hasta que esté autorizado o falle * * @param string $transactionID * @return array ['success' => bool, 'codigo' => int, 'mensaje' => string] */ public function esperarAutorizacion($transactionID) { // Espera inicial sleep($this->initialDelay);
$attempt = 0;
while ($attempt < $this->maxAttempts) { $attempt++;
try { $response = $this->soapClient->documentStatus($transactionID);
// Log del intento Zend_Log::debug("documentStatus intento #$attempt", [ 'transaction_id' => $transactionID, 'codigo' => $response['codigo'], 'mensaje' => $response['mensaje'] ]);
// Documento autorizado if ($response['codigo'] == 200) { Zend_Log::info("Documento autorizado: $transactionID"); return [ 'success' => true, 'codigo' => 200, 'mensaje' => $response['mensaje'] ]; }
// Error en procesamiento if ($response['codigo'] == 400 || $response['codigo'] == 500) { Zend_Log::error("Error en documento: $transactionID", [ 'codigo' => $response['codigo'], 'mensaje' => $response['mensaje'], 'error' => $response['mensajeError'] ]); return [ 'success' => false, 'codigo' => $response['codigo'], 'mensaje' => $response['mensaje'], 'error' => $response['mensajeError'] ]; }
// Aún en proceso (202) - continuar polling $waitTime = $this->calculateWaitTime($attempt); sleep($waitTime);
} catch (Exception $e) { Zend_Log::error("Error en polling: " . $e->getMessage());
// Si es error de conexión, reintentar if ($this->isConnectionError($e)) { sleep($this->pollingInterval); continue; }
throw $e; } }
// Timeout alcanzado Zend_Log::error("Timeout alcanzado para transactionID: $transactionID"); return [ 'success' => false, 'codigo' => 408, 'mensaje' => 'Timeout: El documento no fue autorizado en el tiempo esperado' ]; }
/** * Calcula tiempo de espera con backoff progresivo */ private function calculateWaitTime($attempt) { if ($attempt < 10) { return 3; // Primeros 30 segundos: cada 3s } elseif ($attempt < 30) { return 5; // Siguiente minuto: cada 5s } else { return 10; // Después: cada 10s } }
private function isConnectionError($exception) { $message = $exception->getMessage(); return ( strpos($message, 'timeout') !== false || strpos($message, 'connection') !== false || strpos($message, 'network') !== false ); }}
<?phpfunction actualizarEstadoNomina($transactionID, $response){ $db = Zend_Db_Table::getDefaultAdapter();
$data = [ 'updated_at' => new Zend_Db_Expr('NOW()') ];
switch ($response['codigo']) { case 200: $data['estado'] = 'AUTORIZADO'; $data['fecha_autorizacion'] = new Zend_Db_Expr('NOW()'); $data['mensaje_autorizacion'] = $response['mensaje']; break;
case 202: $data['estado'] = 'EN_PROCESO'; $data['intentos_consulta'] = new Zend_Db_Expr('intentos_consulta + 1'); break;
case 400: case 500: $data['estado'] = 'ERROR'; $data['ultimo_error'] = $response['mensaje']; $data['detalle_error'] = $response['mensajeError'] ?? null; break;
case 404: $data['estado'] = 'NO_ENCONTRADO'; $data['ultimo_error'] = 'TransactionID inválido'; break; }
$db->update( 'nomina_electronica', $data, $db->quoteInto('transaction_id = ?', $transactionID) );}Problema: El transactionID no existe en el sistema.
{ "codigo": 404, "mensaje": "Documento no encontrado"}Causas posibles:
Solución:
// Verificar transactionID en BD$nomina = $db->fetchRow( "SELECT * FROM nomina_electronica WHERE transaction_id = ?", [$transactionID]);
if (!$nomina) { // TransactionID no está en nuestra BD throw new Exception('TransactionID no registrado localmente');}
// Verificar formatoif (!preg_match('/^[a-f0-9]{64}$/i', $transactionID)) { throw new Exception('Formato de TransactionID inválido');}Problema: La DIAN rechazó el documento.
{ "codigo": 400, "mensaje": "Error en validación", "mensajeError": "El total de deducciones no coincide con la suma de conceptos"}Solución: Analizar mensajeError y corregir.
// Marcar nómina para corrección$db->update('nomina_electronica', [ 'estado' => 'REQUIERE_CORRECCION', 'ultimo_error' => $response['mensaje'], 'detalle_error' => $response['mensajeError'], 'requiere_atencion' => 1], "transaction_id = '$transactionID'");
// Notificar al equipo$this->notificarErrorValidacion($nominaId, $response['mensajeError']);Problema: El documento no se autoriza en tiempo razonable.
Estrategia:
// Si pasan 10 minutos sin autorizaciónif ($attempt >= $maxAttempts) { // Marcar como pendiente de revisión $db->update('nomina_electronica', [ 'estado' => 'TIMEOUT', 'ultimo_error' => 'Documento no autorizado en tiempo esperado', 'requiere_revision_manual' => 1 ], "transaction_id = '$transactionID'");
// Crear ticket de soporte $this->crearTicketSoporte( "Timeout en autorización de nómina", "TransactionID: $transactionID - Revisar en Facturatech" );}<?phpclass Nomina_Metrics_DocumentStatus{ public function getTiempoPromedioAutorizacion() { $db = Zend_Db_Table::getDefaultAdapter();
return $db->fetchOne(" SELECT AVG( TIMESTAMPDIFF(SECOND, fecha_envio, fecha_autorizacion) ) as promedio_segundos FROM nomina_electronica WHERE estado = 'AUTORIZADO' AND fecha_autorizacion IS NOT NULL AND fecha_envio >= DATE_SUB(NOW(), INTERVAL 30 DAY) "); }
public function getTasaExito() { $db = Zend_Db_Table::getDefaultAdapter();
$total = $db->fetchOne(" SELECT COUNT(*) FROM nomina_electronica WHERE fecha_envio >= DATE_SUB(NOW(), INTERVAL 30 DAY) ");
$exitosos = $db->fetchOne(" SELECT COUNT(*) FROM nomina_electronica WHERE estado = 'AUTORIZADO' AND fecha_envio >= DATE_SUB(NOW(), INTERVAL 30 DAY) ");
return ($total > 0) ? ($exitosos / $total * 100) : 0; }
public function getDistribucionTiempos() { $db = Zend_Db_Table::getDefaultAdapter();
return $db->fetchAll(" SELECT CASE WHEN TIMESTAMPDIFF(SECOND, fecha_envio, fecha_autorizacion) <= 10 THEN '0-10s' WHEN TIMESTAMPDIFF(SECOND, fecha_envio, fecha_autorizacion) <= 30 THEN '10-30s' WHEN TIMESTAMPDIFF(SECOND, fecha_envio, fecha_autorizacion) <= 60 THEN '30-60s' ELSE '>60s' END as rango, COUNT(*) as cantidad FROM nomina_electronica WHERE estado = 'AUTORIZADO' AND fecha_autorizacion IS NOT NULL AND fecha_envio >= DATE_SUB(NOW(), INTERVAL 7 DAY) GROUP BY rango "); }}Una vez que el documento tenga código 200 (autorizado):
Proceder a descargar los documentos firmados:
downloadXML - XML con firma DIANdownloadPDF - Representación gráficadownloadCUNE - Código único