1. Go to this page and download the library: Download ionysdev/pkuatia library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
ionysdev / pkuatia example snippets
use IonysDev\Pkuatia\Core\Config;
use IonysDev\Pkuatia\Sifen;
$config = new Config();
// Ambiente (dev o prod)
$config->env = Config::ENV_DEV; // o Config::ENV_PROD para producción
// Formato del certificado (pem, p12 o pfx)
$config->certificateFormat = "pem"; // o "p12" / "pfx"
$config->privateKeyPassphrase = "tu_contraseña";
// PEM en un solo archivo (certificado + clave privada):
$config->privateKeyFilePath = "/ruta/a/tu/certificado.pem";
// PEM en dos archivos separados:
// $config->certificateFilePath = "/ruta/a/tu/certificado.crt";
// $config->privateKeyFilePath = "/ruta/a/tu/clave_privada.key";
// P12/PFX (certificado y clave en un solo archivo):
// $config->certificateFormat = "pfx";
// $config->privateKeyFilePath = "/ruta/a/tu/certificado.pfx";
// Credenciales del SIFEN
$config->idCsc = "0001"; // ID CSC proporcionado por el SIFEN
$config->csc = "ABCD0000000000000000000000000000"; // CSC de 32 caracteres (este es el de pruebas: en producción Init() exige el real)
// Ruta para almacenar el archivo de control del dId (opcional)
$config->dIdFilePath = "PKuatiaDId.dat.json";
// Opcionales (0.3.0)
// $config->validarXsdAntesDeEnviar = true; // EnviarDE, EnviarLoteDE y RegistrarEvento validan contra el XSD antes de tocar la red
// $config->soapTrace = false; // no guardar la última solicitud/respuesta (GetLastRequest/GetLastResponse devuelven null)
// Inicializar Sifen
Sifen::Init($config);
// Al terminar (opcional): borra los temporales del PKCS#12, descarta el cliente SOAP y olvida la clave privada
Sifen::Shutdown();
use IonysDev\Pkuatia\Core\Responses\RResEnviConsRUC;
use IonysDev\Pkuatia\Sifen;
// RUC sin dígito verificador (5 a 8 dígitos)
$respuesta = Sifen::ConsultarRUC('80012345');
if ((int) $respuesta->getDCodRes() === RResEnviConsRUC::COD_RES_RUC_FOUND) {
$ruc = $respuesta->getRContRuc();
echo $ruc->dRazCons . ' — facturador electrónico: ' . $ruc->dRUCFactElec;
} else {
echo 'Error: ' . $respuesta->getDMsgRes();
}
use IonysDev\Pkuatia\Sifen;
// CDC de 44 dígitos del DE ya autorizado
$cdc = '01800000005001001000000122026010100000000000001'; // placeholder
$respuesta = Sifen::ConsultarDE($cdc);
if ($respuesta->getDCodRes() === 422) { // 0422: existe y está aprobado; 0420: no existe o no está aprobado
$contenido = $respuesta->getRContDe();
$de = $contenido->getRDe()->getDE(); // el DE completo, como objetos
echo $de->getGDatGralOpe()->getGEmis()->getDNomEmi() . ', protocolo ' . $contenido->getDProtAut() . "\n";
// Cancelación, conformidad y demás eventos registrados sobre el DE, con la respuesta del SIFEN a cada uno
foreach ($contenido->getRContEvs() as $evento) {
echo $evento->getRResEnviEventoDe()->getGResProcEVe()[0]->getDEstRes() . "\n";
}
} else {
echo $respuesta->getDCodRes() . ': ' . $respuesta->getDMsgRes() . "\n";
}
// La respuesta tal como llegó, para guardarla o auditarla, también cuando el parseo falla
$xmlCrudo = Sifen::GetLastResponse();
use IonysDev\Pkuatia\Core\Fields\DE\AA\RDE;
$rde = RDE::FromSimpleXMLElement(simplexml_load_string($xmlFirmado));
$xml = $rde->toXMLString(); // el mismo rDE, con Signature y gCamFuFD; la firma sigue verificando
use IonysDev\Pkuatia\Validacion\OrigenError;
use IonysDev\Pkuatia\Validacion\ValidadorDE;
$resultado = $factura->validarReglas(); // o ValidadorDE::validar($rde) para un DE o RDE ya armado
if (!$resultado->esValido()) {
foreach ($resultado->getErrores() as $error) {
// "1309 [D207 dDVRec] Dígito Verificador del RUC del receptor incorrecto ... Ver setReceptor()."
echo $error . "\n";
// o por partes: $error->codigoSifen, $error->campoMT, $error->elemento, $error->ruta, $error->valor, $error->sugerencia
}
}
foreach ($resultado->getAdvertencias() as $aviso) { // lo que el SIFEN aprobaría con observación
echo 'Aviso: ' . $aviso->mensaje . "\n";
}
$soloReglas = $resultado->porOrigen(OrigenError::Regla); // también porCodigo('1309') y porCampo('D207')
$resultado->lanzarSiInvalido(); // DocumentoInvalidoException, con el resultado en getResultado()
use IonysDev\Pkuatia\Sifen;
$xml = Sifen::FirmarDE($rde, new DateTime('now', new DateTimeZone('America/Asuncion')));
$resultado = Sifen::ValidarXSD($xml); // Sifen::ValidarEventoXSD($sobre) para un sobre de eventos; sin Init() ni red
foreach ($resultado as $error) {
echo "línea {$error->linea}: {$error->mensaje} ({$error->campoMT}) {$error->sugerencia}\n";
}
use IonysDev\Pkuatia\Sifen;
$verificacion = Sifen::VerificarFirma($xmlRecibido); // un rDE o un sobre de eventos; sin Init() ni red
if ($verificacion->esValida()) {
$firma = $verificacion->getFirmas()[0];
echo $firma->certificado?->sujeto . ' (RUC ' . $firma->certificado?->ruc . '), firmado el ' . $firma->fechaFirma?->format('c') . "\n";
} else {
foreach ($verificacion->getErrores() as $error) {
echo $error . "\n"; // 0140 estándar, 0141 digest o firma, 0120 certificado, 0121/2450 vigencia
}
}
use IonysDev\Pkuatia\Sifen;
use IonysDev\Pkuatia\Validacion\CatalogoValidaciones;
$respuesta = Sifen::EnviarDE($xml);
if ($respuesta->esAprobado()) {
echo 'Aprobado, protocolo ' . $respuesta->getProtocolo() . "\n";
} else {
foreach ($respuesta->getMotivos() as $motivo) {
$info = $motivo->getInfoValidacion(); // catálogo del MT: campo, regla y método de PKuatia que lo carga
echo $motivo->getCodigo() . ' ' . $motivo->getMensaje() . ($info !== null ? " [{$info->id}] {$info->observacion} → {$info->metodoPkuatia}" : '') . "\n";
}
}
// El catálogo también se consulta solo: qué significa un código y qué tocar
echo CatalogoValidaciones::describir('1321'); // "1321 (D208c): Receptor innominado ..."; como objeto: ->observacion (la regla) y ->metodoPkuatia
use IonysDev\Pkuatia\Core\Exceptions\PkuatiaExceptionInterface;
use IonysDev\Pkuatia\Core\Exceptions\SifenNoDisponibleException;
use IonysDev\Pkuatia\Core\Exceptions\TransporteException;
use IonysDev\Pkuatia\Sifen;
try {
$respuesta = Sifen::EnviarDE($xml);
} catch (SifenNoDisponibleException $e) {
// esperar y reintentar: el SIFEN no respondió (WSDL o cuerpo vacío)
} catch (TransporteException $e) {
echo $e->getTipo()->value . ' en ' . $e->getOperacion() . ': ' . $e->getMessage();
} catch (PkuatiaExceptionInterface $e) {
echo get_class($e) . ': ' . $e->getMessage(); // configuración, certificado, validación o respuesta ilegible
}
use IonysDev\Pkuatia\Core\Constants\TimbTiDE;
TimbTiDE::Factura->value; // 1
TimbTiDE::NotaDeCredito->value; // 5
use DateTime;
use DateTimeZone;
use IonysDev\Pkuatia\Core\Config;
use IonysDev\Pkuatia\Core\Constants\CamCondOpe;
use IonysDev\Pkuatia\Core\Constants\CamFEIndPres;
use IonysDev\Pkuatia\Core\Constants\CamIVAAfecIVA;
use IonysDev\Pkuatia\Core\Constants\CamIVATasaIVA;
use IonysDev\Pkuatia\Core\Constants\EmisRecTipCont;
use IonysDev\Pkuatia\Core\Constants\OpeComTipTrans;
use IonysDev\Pkuatia\Core\Constants\PaConEIniTiPago;
use IonysDev\Pkuatia\Core\Constants\RecTiOpe;
use IonysDev\Pkuatia\Core\DocumentosElectronicos\Factura;
use IonysDev\Pkuatia\Sifen;
// 1. Configuración e inicialización (ver sección Configuración)
$config = new Config();
$config->env = Config::ENV_DEV;
$config->certificateFormat = 'pem';
$config->privateKeyFilePath = '/ruta/a/certificado.pem';
$config->privateKeyPassphrase = 'tu_contraseña';
$config->idCsc = '0001';
$config->csc = 'ABCD0000000000000000000000000000';
$config->wsdlCacheEnabled = true;
Sifen::Init($config);
// 2. Conformar la factura
$factura = new Factura(CamCondOpe::Contado);
$factura->setTimbrado(
numTimb: 12345678,
fechaInicio: new DateTime('2024-01-01'),
numEst: 1,
numExp: 1,
numDoc: 1
);
$factura->setFechaEmision(new DateTime('now', new DateTimeZone('America/Asuncion')));
$factura->setEmisor(
rucEmisor: '80000000',
dv: 5,
tipoContribuyente: EmisRecTipCont::PersonaJuridica,
tipoRegimen: null,
nombreEmisor: 'Empresa Emisora de Ejemplo SA',
nombreFantasia: null,
callePrincipal: 'Av. Principal',
casaNro: '100',
calleSecundaria: null,
complementoDir: null,
codDep: 1,
codDistrito: null,
codCiud: 1,
telefono: '021000000',
email: '[email protected]',
nombreSucursal: null
);
$factura->addEmisorActividadEconomica(47190, 'Comercio al por menor');
$factura->setReceptor(
nombre: 'Cliente Receptor SA',
esContribuyente: true,
tipoOperacion: RecTiOpe::B2B,
codPais: 'PRY',
tipoContribuyente: EmisRecTipCont::PersonaJuridica,
ruc: '80012345',
dv: 0,
tipoIdentificacion: null,
nroIdentificacion: null,
nombreFantasia: null,
callePrincipal: null,
numeroCasa: null,
codigoDepartamento: null,
codigoDistrito: null,
codigoCiudad: null,
telefono: null,
celular: null,
email: null,
codigoDeCliente: null
);
$factura->setTipoDeTransaccion(OpeComTipTrans::VentaMercaderia);
$factura->setIndicadorPresencia(CamFEIndPres::Presencial);
$factura->addItem(
codigo: 'PROD001',
descripcion: 'Producto de ejemplo',
codUnidadMedida: 77, // UNI
cantidad: '1',
precisionMoneda: 0,
precioUnit: '100000',
totalBruto: null,
afectIVA: CamIVAAfecIVA::Gravado,
proporcionGravadaIVA: '100',
tasaDeIVA: CamIVATasaIVA::IVA10
);
// Totales (gTotSub): obligatorio antes de serializar. El argumento es la precisión de la moneda (0 para PYG).
$factura->calcTotSub(0);
$factura->addPago(PaConEIniTiPago::Efectivo, '100000', 'PYG');
// 3. Firmar y enviar
$rde = $factura->facturaToRDE();
$fechaFirma = new DateTime('now', new DateTimeZone('America/Asuncion'));
$xmlFirmado = Sifen::FirmarDE($rde, $fechaFirma);
$respuesta = Sifen::EnviarDE($xmlFirmado);
$prot = $respuesta->getRProtDe();
$cdc = $prot->getId();
$resultado = $prot->getGResProc()[0];
echo "CDC: $cdc — {$resultado->dCodRes}: {$resultado->dMsgRes}\n";
// Aprobación habitual: dCodRes en familia 026x (p. ej. 0260)
use IonysDev\Pkuatia\Sifen;
// $cdc obtenido del envío anterior (44 dígitos)
$cdc = '01800000005001001000000122026010100000000000001'; // placeholder
$respuesta = Sifen::CancelarDE(
$cdc,
'Anulación por error en los datos del comprobante'
);
foreach ($respuesta->getGResProcEVe() as $evento) {
foreach ($evento->getGResProc() as $proc) {
echo "{$proc->dCodRes}: {$proc->dMsgRes}\n";
// Éxito habitual del evento de cancelación: 0600
}
}
use IonysDev\Pkuatia\Core\Responses\RResEnviLoteDe;
use IonysDev\Pkuatia\Sifen;
// Hasta 50 rDE firmados con FirmarDE, del mismo tipo y del mismo RUC emisor, sin CDC repetidos.
// EnviarLoteDE lo valida antes de enviar: el SIFEN rechaza esos lotes o bloquea temporalmente al RUC.
$recepcion = Sifen::EnviarLoteDE([$xmlFirmado1, $xmlFirmado2]);
if ($recepcion->getDCodRes() === RResEnviLoteDe::COD_RES_ACEPTADO) { // 0300
$nroLote = $recepcion->getDProtConsLoteString(); // guardarlo como texto: tiene 17 dígitos o más
// Bloquea el proceso: primera consulta a los 10 minutos y después cada 10, mientras siga en 0361
$resultado = Sifen::EsperarResultadoLote($nroLote);
foreach ($resultado->getGResProcLote() as $documento) {
echo $documento->getId() . ': ' . $documento->getDEstRes() . "\n"; // Aprobado, Aprobado con observación o Rechazado
}
}
bash
composer install
php test/SmokeTest.php # smoke test rápido
composer test # PHPUnit (tests/Unit/)
composer phpstan # análisis estático nivel 1
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.