Skip to content

documentStatus

POST SOAP 1.1 Polling Required

El método documentStatus permite consultar el estado de procesamiento de un documento previamente enviado con uploadDocument. Este endpoint es crucial porque:

  • Verifica si el documento ya fue firmado por la DIAN
  • Permite conocer si hubo errores en el procesamiento
  • Es necesario antes de descargar XML/PDF firmados
  • Debe consultarse mediante polling (no hay webhooks)

ParámetroTipoObligatorioDescripción
usernamestringOKUsuario de acceso al servicio
passwordstringOKContraseña encriptada en SHA256
transaccionIDstringOKID retornado por uploadDocument (64 caracteres hex)

Request SOAP completo
<?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>

Response SOAP - Documento firmado
<documentStatusResponse>
<codigo>200</codigo>
<mensaje>La nómina NOM35921 ha sido autorizada</mensaje>
<mensajeError></mensajeError>
</documentStatusResponse>
Response SOAP - Aún procesando
<documentStatusResponse>
<codigo>202</codigo>
<mensaje>Documento en proceso de firma</mensaje>
<mensajeError></mensajeError>
</documentStatusResponse>
Response SOAP - Error de validación
<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:

  • Errores de cálculo
  • Datos inconsistentes
  • Validaciones de negocio fallidas

Acción: Revisar mensajeError y corregir documento

404 - No Encontrado

TransactionID inválido o inexistente

Causas posibles:

  • TransactionID incorrecto
  • Documento muy antiguo (expirado)
  • Error de tipeo

Acción: Verificar transactionID almacenado


  1. Espera Inicial
    Esperar 2 segundos después de uploadDocument antes del primer intento.

  2. Polling con Intervalos
    Consultar cada 2 segundos durante los primeros 20 segundos.

  3. Backoff Progresivo
    Después de 20s, aumentar intervalo a 3 segundos.

  4. Timeout Final
    Máximo 4 minutos de espera antes de marcar como error.

PollingService.php
<?php
class 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
);
}
}

Logo de starlight
Actualizar estado según respuesta
<?php
function 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:

  1. TransactionID copiado incorrectamente
  2. Documento expirado (muy antiguo)
  3. Error en base de datos local

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 formato
if (!preg_match('/^[a-f0-9]{64}$/i', $transactionID)) {
throw new Exception('Formato de TransactionID inválido');
}

Calcular métricas
<?php
class 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
");
}
}

  • Sistema de polling implementado
  • Backoff progresivo configurado
  • Timeout de 10 minutos establecido
  • Estados en BD actualizados correctamente
  • Logs detallados de cada intento
  • Métricas de tiempo calculadas
  • Alertas configuradas para timeouts
  • Notificaciones de errores 400
  • Dashboard de monitoreo
  • Proceso de revisión manual para timeouts

Una vez que el documento tenga código 200 (autorizado):

Proceder a descargar los documentos firmados: