Saltar al contenido
dte-sdk

Obtener folios del SII

Todo documento necesita un folio, y un folio sale de un CAF (Código de Autorización de Folios): un archivo con un rango de folios y la llave privada que firma el timbre de cada documento. Lo obtienes en el portal del SII. Este ejemplo lo hace desde código con dte-sdk/portal: inicia sesión con tu certificado digital, lee quién eres para el SII, revisa cuántos folios te daría el SII, solicita un CAF, lo guarda de forma segura, lleva la cuenta de los folios con un registro local y emite un documento con el primero.

La 0.8.0 de dte-sdk está en npm, la versión más reciente (ver Instalación). Todavía no se recomienda para producción: la mayoría de las áreas no se ha probado contra el SII real, y hasta la 1.0.0 la API puede cambiar en cualquier versión. Las funciones del portal se probaron contra páginas sintéticas, con un límite importante: el inicio y cierre de sesión, los datos del contribuyente y las páginas de rechazo se midieron contra el SII real, pero las páginas de folios y CAF de una empresa inscrita todavía no se han visto, porque necesitan una empresa inscrita como emisor. El cupo, las solicitudes de CAF, la anulación y la reobtención están escritos a partir de lo que se sabe que hace el portal. Lee Estado, pruébalo en certificación y espera sorpresas.

El XML del CAF es un secreto. Contiene la llave privada que firma cada timbre del rango: quien tenga el archivo puede emitir documentos a tu nombre. Guárdalo donde solo tu servicio pueda leerlo, nunca en un repositorio, un log ni un reporte de errores.

Qué vas a construir#

Un programa, folios.ts, que:

  • abre el portal como titular del certificado y lee el perfil de la empresa: nombre, giro, dirección y la resolución del ambiente,
  • lee el cupo de folios del tipo 33, sin timbrar nada,
  • solicita un CAF de hasta 50 folios, y lee cuántos le otorgaron de verdad,
  • guarda el XML con permisos restringidos, lo parsea y reserva un folio en el registro local,
  • emite una factura con ese folio y cierra la sesión del portal.

Enviar la factura es tarea de Emitir una factura.

Requisitos previos#

  • Node 22.12 o superior, en un proyecto de módulos ES, con dte-sdk instalado. El portal necesita Node: fetch no puede presentar un certificado cliente. Ver Instalación.
  • Tu certificado digital como firma.pfx, con su contraseña en PFX_PASSWORD.
  • El certificado debe estar habilitado como método de ingreso en sii.cl. Es un paso manual en el SII, en Cambiar Clave y datos de autenticación, opción "Utilizando Clave Tributaria o Certificado Digital". Mientras no lo hagas, todo inicio de sesión lanza SII_PORTAL_CERT_NOT_ENABLED.
  • Una empresa inscrita como emisor electrónico en el ambiente que uses, y el titular del certificado con permiso para solicitar folios por ella. Sin eso el SII rechaza con los motivos de la tabla de abajo.
  • Los RUT y nombres del ejemplo son ficticios: usa tu propia empresa.

El programa completo#

TypeScript
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { isDteError, issueDocument, loadCertificateFromPkcs12, parseCaf } from 'dte-sdk'
import { createDirectoryFolioRegistry } from 'dte-sdk/node'
import {
  closePortalSession,
  createPortalClient,
  fetchIssuerProfile,
  portalRejectionMessage,
  queryFolioQuota,
  requestCaf,
  type PortalRejection,
} from 'dte-sdk/portal'
 
const COMPANY_RUT = '76543210-3'
const TYPE = 33
const WANTED = 50 // folios que quisiéramos
const LEAST = 10 // menos que esto no vale la pena: el CAF quedaría sin usar y bajaría el próximo cupo
 
const certificate = loadCertificateFromPkcs12(await readFile('firma.pfx'), process.env.PFX_PASSWORD!)
const portal = createPortalClient({ environment: 'certification', certificate, companyRut: COMPANY_RUT })
const folios = createDirectoryFolioRegistry({ dir: './data/folios' })
 
try {
  // 1. Quiénes somos para el SII en este ambiente
  const profile = await fetchIssuerProfile(portal)
  console.log(`issuer: ${profile.businessName}, resolution ${profile.resolutionNumber} of ${profile.resolutionDate}`)
 
  // 2. Cuántos folios nos daría el SII ahora. Todavía no se timbra nada
  const quota = await queryFolioQuota(portal, { type: TYPE })
  if (quota.blocked) {
    console.log('The SII is not stamping this type now: issue with the folios you already have.')
  } else {
    console.log(
      quota.unrestricted
        ? 'quota: the SII is not rationing this type'
        : `quota: at most ${quota.maxAuthorized}, ${quota.availableFolios} stamped and unused`,
    )
 
    // 3. Pedir el CAF y leer cuántos folios recibimos de verdad
    const quantity = Math.min(WANTED, quota.maxAuthorized ?? WANTED)
    const obtained = await requestCaf(portal, { type: TYPE, quantity, minQuantity: LEAST })
    const { from, to } = obtained.caf.range
    console.log(`CAF obtained: folios ${from}-${to} (${obtained.granted} granted)`)
 
    // 4. El XML contiene la llave privada del CAF: es un secreto. Guárdalo donde solo lo lea este servicio
    const file = `./secrets/caf-${TYPE}-${from}-${to}.xml`
    await mkdir('./secrets', { recursive: true, mode: 0o700 })
    await writeFile(file, obtained.xml, { encoding: 'latin1', mode: 0o600 })
    console.log(`saved to ${file}`)
 
    // 5. Desde aquí cuenta el archivo, como en cualquier corrida posterior
    const caf = parseCaf(await readFile(file, 'latin1'))
    const scope = { companyRut: COMPANY_RUT, type: TYPE, range: caf.range, environment: 'certification' } as const
    const folio = await folios.reserve(scope)
    console.log(`registry: folio ${folio} reserved, ${await folios.remaining(scope)} left`)
 
    // 6. Emitir con él (enviarla es el ejemplo de la factura)
    const invoice = issueDocument({
      document: {
        type: TYPE,
        folio,
        issuer: {
          rut: profile.rut,
          businessName: profile.businessName,
          businessActivity: profile.businessActivity,
          activityCode: profile.activityCode ?? undefined,
          address: profile.address,
          commune: profile.commune,
          city: profile.city,
        },
        receiver: {
          rut: '12345678-5',
          businessName: 'Comercializadora Rapel Ltda',
          businessActivity: 'Comercio',
          address: 'Pasaje Los Olmos 88',
          commune: 'Providencia',
        },
        items: [{ name: 'Plan anual', quantity: 1, unitPrice: 100000 }],
      },
      caf,
      certificate,
    })
    console.log(`invoice issued: folio ${invoice.header.folio}`, JSON.stringify(invoice.header.totals))
  }
} catch (error) {
  if (!isDteError(error)) throw error
  if (error.code === 'SII_PORTAL_REJECTED') {
    // El SII dijo que no, y el SDK explica qué significa y qué hacer
    const rejection = error.details.rejection as PortalRejection
    console.error(portalRejectionMessage(rejection))
    console.error('The portal said:', error.details.portalMessage)
  } else {
    console.error(error.code, error.details)
  }
  process.exitCode = 1
} finally {
  await closePortalSession(portal) // devuelve el cupo de sesión al SII
}

Paso a paso#

1

Crear el cliente del portal

createPortalClient devuelve datos planos, igual que el cliente del SII. Valida las opciones y usa un transporte https de Node que presenta tu certificado y se queda en TLS 1.2. Todavía no hay inicio de sesión: cada función del portal inicia sesión cuando lo necesita y reutiliza la sesión por 90 minutos. El SII permite pocas sesiones por RUT, así que un servicio con varias réplicas comparte la sesión con los puertos sessionCache y lock; los de por defecto viven en el proceso.

2

Leer el perfil de la empresa

fetchIssuerProfile lee la página de resolución y los datos del contribuyente en secuencia y los combina: rut, businessName, businessActivity, activityCode (o null), address, commune, city, resolutionDate y resolutionNumber. Es lo que necesitas para llenar un Issuer y una carátula de sobre sin escribirlos a mano. La resolución es por ambiente: certificación y producción responden fechas y números distintos para el mismo RUT, y un par equivocado hace que se rechace un sobre con "Error en Carátula". Si la empresa no está autorizada como emisor en el ambiente, lanza SII_PORTAL_REJECTED con el rechazo companyNotAuthorized.

Revisa el perfil antes de usarlo: si el portal no tiene giro para la empresa, businessActivity es un string vacío y createDocument lo rechazará con MISSING_FIELD.

3

Leer el cupo antes de pedir

El SII raciona los folios según tu historial de timbraje y emisión. queryFolioQuota se detiene en el formulario que muestra el cupo: no se timbra nada y el cupo no se mueve. El resultado trae maxAuthorized (lo máximo que el SII da ahora) y availableFolios (timbrados y aún sin usar), que son null cuando el SII no está racionando el tipo (unrestricted: true), y blocked: true cuando el SII responde "no se autoriza timbraje". Un tipo bloqueado se levanta emitiendo documentos con los folios que ya tienes; volver a pedir no sirve.

Esto importa porque los folios sin usar pesan en tu contra: bajan lo que el SII autoriza después y, al final, bloquean el timbraje. Pide lo que vas a usar.

4

Solicitar el CAF y leer lo que recibiste

requestCaf recorre las cinco páginas del portal y devuelve el CAF como obtained.xml (el archivo tal como lo entregó el SII), obtained.caf (ya parseado) y obtained.granted, la cantidad de folios del rango.

Lee granted: puede ser menos de lo que pediste. La solicitud se limita a maxAuthorized, y cuando se excede el límite del SII y no hay minQuantity, el SDK pide una vez más por un solo folio. minQuantity es la forma estricta: bajo ese mínimo no se timbra nada y la llamada lanza SII_PORTAL_REJECTED con quotaInsufficient, porque un rango corto quedaría sin usar y bajaría tu próximo cupo. El programa pide 50 y rechaza cualquier cosa bajo 10. maxAuthorized, availableFolios y recovered (el archivo volvió por el flujo de "ya fue autorizado" o por la reobtención) también vienen en el resultado.

5

Guardar el CAF como un secreto

El programa escribe el archivo con modo 0o600 en un directorio con modo 0o700, como latin1: el CAF es un archivo ISO-8859-1. En tu servicio esto es un almacén de secretos, no una carpeta. Lo que guardes debe ser el archivo tal como lo emitió el SII: el bloque <CAF> se copia en cada timbre, y cualquier cambio rompe la firma que revisa el SII. La búsqueda de archivos CAF lo vuelve a leer en la codificación que nombra su declaración XML, ISO-8859-1 para el archivo del SII, así que una Ñ o un acento en el nombre de la empresa sobrevive el viaje de ida y vuelta.

6

Parsearlo y llevar la cuenta de los folios

Desde aquí el programa trabaja con el archivo, como cualquier corrida posterior: parseCaf lo lee y valida (CAF_INVALID si no es un CAF) y te da caf.range. El registro viene de dte-sdk/node: reserve(scope) entrega el siguiente folio libre, markSent registra uno como enviado (como en el ejemplo de la factura), release devuelve folios, markAnnulled registra folios anulados en el SII, remaining(scope) cuenta los libres y stats totaliza una empresa y un tipo. El scope es la empresa, el tipo, el rango del CAF y el ambiente. cafFingerprint(xml), de dte-sdk/portal, se puede agregar al scope para distinguir dos archivos de un mismo rango.

El registro en un directorio es para un solo proceso. Para varios procesos, un servicio con su propia base implementa el tipo FolioRegistry con sus propias transacciones, y createFolioRegistry(store) arma uno sobre un almacén que tú entregas. createMemoryFolioRegistry es para tests. Lo que uses debe sobrevivir a los reinicios: perder el registro repite folios ante el SII.

7

Emitir y cerrar la sesión

issueDocument usa el perfil como emisor. El RUT del CAF debe ser el del emisor (si no, CAF_ISSUER_MISMATCH). La sesión se cierra en finally con closePortalSession, que devuelve el cupo al SII y siempre borra la sesión en caché; si el cierre falla solo se registra, porque el SII vence la sesión por su cuenta.

Resultado esperado#

En una corrida exitosa en certificación, con una empresa que el SII no raciona y un registro nuevo:

Text
issuer: Comercial Andina SpA, resolution 0 of 2026-09-01
quota: the SII is not rationing this type
CAF obtained: folios 1-50 (50 granted)
saved to ./secrets/caf-33-1-50.xml
registry: folio 1 reserved, 49 left
invoice issued: folio 1 {"netAmount":100000,"vatRate":19,"vatAmount":19000,"totalAmount":119000}

La última línea la calcula el SDK: 1 × 100000 neto, IVA 19000, total 119000. Todo lo anterior viene del SII y es un ejemplo: el nombre, la resolución, el cupo y el rango serán los tuyos. Con racionamiento, la línea del cupo dice quota: at most 30, 0 stamped and unused, y el CAF tiene como máximo esa cantidad de folios.

Elegir el CAF en una corrida posterior#

Un programa que corre todos los días no solicita un CAF cada vez: elige uno que ya tiene. resolveCafFile, de dte-sdk/node, busca en un directorio (y en sus carpetas hasta cinco niveles) el archivo CAF más nuevo del tipo y la empresa que no haya vencido y que todavía tenga required folios libres en el registro. Devuelve el archivo con el scope desde el que reservar, o null cuando ninguno cumple: ese es el momento de llamar a requestCaf.

TypeScript
import { createDirectoryFolioRegistry, resolveCafFile } from 'dte-sdk/node'
 
const folios = createDirectoryFolioRegistry({ dir: './data/folios' })
const file = await resolveCafFile({
  dir: './secrets',
  type: 33,
  issuerRut: '76543210-3',
  required: 1,
  registry: folios,
  environment: 'certification',
})
if (file === null) throw new Error('No hay CAF con folios libres: solicita uno')
const folio = await folios.reserve(file.scope!) // file.caf va a issueDocument

La empresa se lee del archivo, nunca de la carpeta donde está, así que un CAF de otra empresa en el mismo directorio no se devuelve. El scope que entrega incluye el cafFingerprint del archivo, y un alcance sin él es otra entrada del registro: si eliges los archivos así, agrega cafFingerprint: cafFingerprint(obtained.xml) (de dte-sdk/portal) al alcance del paso 5 del programa, para que ambos usen la misma entrada, o arma ese alcance con cafFileScope(obtained, 'certification') de dte-sdk/node, el que devuelve resolveCafFile. Cualquiera de los dos coincide con el archivo guardado porque el programa guarda obtained.xml sin cambios; un CAF reescrito antes de guardarse toma su alcance del texto guardado. findCafFiles lista todos los archivos utilizables, del más nuevo al más viejo, y findLatestCafFile entrega el primero, sin registro. Con includeExpired: true, findCafFiles también lista los archivos vencidos, marcados expired, para que puedas decir por qué uno no se usa; los otros dos nunca toman uno.

Un registro llevado con resolveCafFile antes de la 0.7.0 puede tener entradas con una huella antigua: un archivo guardado con una marca de orden de bytes UTF-8, o declarado ISO-8859-1 con una Ñ o un acento, se lee distinto ahora, y su rango se leería como libre. Mueve esas entradas al alcance nuevo antes de volver a emitir con un archivo así, como lo explica Archivos CAF.

Otras formas de obtener folios#

requestCaf es el camino directo. Cuando el SII raciona los folios o la cantidad no calza, tres flujos de dte-sdk/portal cubren los casos. Usan el cliente portal del programa de arriba y corren dentro de una sola sesión.

TypeScript
import { createSiiClient, folioReceived } from 'dte-sdk'
import { requestCafExact, requestCafInBatches, reobtainFolios } from 'dte-sdk/portal'
 
const sii = createSiiClient({ environment: 'certification', certificate, companyRut: COMPANY_RUT })
 
// un CAF por toda la cantidad, o nada
const exact = await requestCafExact(portal, { type: 33, quantity: 50 })
 
// lo mismo, pero anulando folios sin usar si eso levanta el límite (no se puede deshacer)
const forced = await requestCafExact(portal, {
  type: 33,
  quantity: 50,
  allowAnnul: true,
  exclude: [{ from: 1, to: 100 }], // un CAF que todavía se usa nunca se toca
  knownAnnulled: [], // lo que devolvió una corrida anterior en knownAnnulled
})
 
// varios CAF cuando el SII raciona por historial
const batches = await requestCafInBatches(portal, { type: 33, quantity: 200, maxBatches: 4 })
 
// rangos que el SII ya autorizó, sin gastar cupo
const back = await reobtainFolios(portal, {
  type: 33,
  quantity: 20,
  isFolioFree: async (folio) => (await folioReceived(sii, { type: 33, folio })) === false, // comprobar con el SII qué folios están libres
})
 
// aquí un rechazo es un dato: los CAF ya obtenidos no se pierden
for (const result of [exact, forced, batches, back]) {
  console.log(result.complete, result.cafs.length)
}
if (!exact.complete) console.log(exact.rejection, exact.portalMessage)
// una tanda cortada por una falla, no por el SII, lo dice en failure
if (!batches.complete) console.log(batches.rejection ?? batches.failure)
// también una solicitud exacta tras su anulación y una reobtención; guarda knownAnnulled igual
if (forced.failure !== null) console.log(forced.failure, forced.knownAnnulled)
if (back.failure !== null) console.log(back.failure, back.reason)
FunciónQué haceCuándo usarla
requestCafExactSolicita un CAF que cubra toda la cantidad; bajo ella no se timbra nada. Con allowAnnul: true, un límite causado por folios sin usar se levanta anulando rangos (hasta 20 por pasada y cuatro pasadas), nunca los de exclude, y pide una vez más.Necesitas todos los folios en un solo archivo.
requestCafInBatchesCubre una cantidad con varios CAF, pidiendo cada vez lo que permite maxAuthorized, hasta maxBatches (4 por defecto). Se detiene apenas una tanda no aporta, porque cada entrega sube availableFolios y puede bajar el próximo maxAuthorized.El SII raciona por historial, no hay nada que anular y esperar no sirve.
reobtainFoliosRecupera rangos que el SII ya autorizó, sin gastar cupo, partiendo por el folio más antiguo. isFolioFree confirma cada folio en el SII, normalmente con folioReceived; isIssued omite rangos que ya enviaste. Un CAF vencido se omite y se informa en expired.Se perdió el archivo del CAF, o el SII te pide emitir con los folios que tienes antes de timbrar más.

Devuelven un resultado en vez de lanzar los rechazos del SII, así los CAF ya obtenidos no se pierden. requestCafExact y requestCafInBatches devuelven complete, cafs (cada uno con su xml secreto), granted, rejection (un PortalRejection o null), portalMessage (lo que dijo el portal) y failure; guarda cada CAF de cafs antes de leer rejection. reobtainFolios devuelve complete, cafs, ranges, expired, available, reason y failure, y una falla al listar los rangos se lee como sin rangos, con la falla en reason. Inicia sesión a pedido, como las demás. Las fallas de sesión, bloqueo y transporte siguen siendo errores, salvo cuando un flujo ya tiene algo que no puedes perder: entonces requestCafInBatches termina con los CAF, rejection: null y failure: { code, operation }, reobtainFolios con los CAF que descargó y failure, y requestCafExact, una vez que el SII respondió su anulación, con knownAnnulled, rejection: null y failure, así que lee también failure. El error original va al logger del cliente, con sus textos tachados y sin lo que dijo una página ni su dirección. Un requestCaf que falló pudo otorgarse igual: revisa listReobtainableRanges antes de pedir de nuevo.

Anular no se puede deshacer. requestCafExact anula solo con allowAnnul: true, y el portal cuenta los folios anulados contra tu cupo por seis meses. exclude protege un CAF que todavía se usa, y knownAnnulled debe guardarse, venga o no failure, y devolverse de una corrida a la siguiente, porque el portal sigue listando los rangos anulados para siempre. No actives allowAnnul hasta estar seguro de que el rango no se usa.

Qué puede salir mal#

Un rechazo del SII es SII_PORTAL_REJECTED, con details.rejection (el motivo) y details.portalMessage (lo que dijo el portal). portalRejectionMessage(rejection) da el significado y qué hacer, en español, para el operador. Los flujos compuestos devuelven los mismos motivos en rejection en vez de lanzar.

rejectionQué significa
stampingBlockedEl SII no autoriza timbraje: tienes suficientes folios sin usar. Emite documentos con ellos. Anular pesa contra el cupo por seis meses.
quotaInsufficientEl SII autoriza menos folios de los que exigiste (minQuantity), porque hay folios timbrados sin usar. Emite con ellos, anúlalos, o pide en tandas.
quotaExceededLa cantidad excede el máximo que el SII autoriza por solicitud para este tipo.
quotaCapReached, foliosShortEl SII otorgó menos folios de los requeridos y no hay más cupo en esta pasada. quotaCapReached llega solo después de una entrega, cuando se rechazó un folio por sobre el máximo; cualquier otro rechazo de una tanda posterior conserva su propio motivo.
noAnnullableFoliosEl timbraje está limitado y no se pueden anular folios: emite con lo que tienes o espera a que el SII recalcule.
stampingUncertainEl SII volvió a mostrar el formulario de solicitud, sin folios y sin motivo, así que no se sabe si autorizó el rango. No se envió nada dos veces. Revisa los rangos que se pueden reobtener (listReobtainableRanges) antes de pedir de nuevo.
rangeAlreadyAuthorizedEl SII ya autorizó ese rango en una solicitud anterior y no devolvió el archivo: reobtenlo desde el portal. requestCafInBatches también lo informa en una tanda posterior.
rangeAnnulledEl rango está anulado: el SII rechazaría cualquier documento con esos folios.
companyNotAuthorizedLa empresa no está autorizada como emisor electrónico en este ambiente: falta la certificación o la postulación. No se arregla con permisos de usuario.
optionNotAuthorizedEl SII responde "no está autorizado para ingresar a esta opción" sin indicar la causa. Lo más frecuente es que la empresa aún no esté autorizada en este ambiente; en facturas, guías y notas revisa también la Verificación de Actividades.
userWithoutPermissionEl RUT del certificado no tiene permisos en la empresa en este ambiente: hay que asignarle los permisos de firmar, enviar y obtener folios. El SII también lo dice como "No está autorizado para solicitar timbraje".
personWithPendingIssuesLa persona tiene situaciones pendientes con el SII y no puede hacer trámites de factura electrónica. Reintentar no lo arregla.
activityVerificationPendingEl RUT no registra Verificación de Actividades para este tipo de documento. Se hace en línea en sii.cl. No aplica a boletas.
typeNotEnabledEl SII no ofrece este tipo de documento para timbraje con este RUT: falta certificarlo o habilitarlo.
inPersonRequiredLa inscripción debe completarse en persona: el representante legal debe ir a la oficina del SII del domicilio de la empresa.

Los otros códigos que puedes encontrar:

CódigoQué hacer
SII_PORTAL_CERT_NOT_ENABLEDHabilita el certificado como método de ingreso en sii.cl.
SII_PORTAL_SESSION_LIMITEl SII informa demasiadas sesiones para este RUT. Por defecto el cliente cierra las sesiones anteriores cuando el SII lo ofrece (closePreviousSessions). Llama siempre a closePortalSession.
SII_PORTAL_LOGIN_FAILED, SII_PORTAL_SESSION_EXPIREDEl inicio de sesión no dio sesión, o el portal sigue pidiendo autenticarse. Una página de ingreso en respuesta a un rango o a su archivo, al reobtener, también es SII_PORTAL_SESSION_EXPIRED.
SII_PORTAL_BLOCKEDEl firewall del SII o un captcha bloqueó la petición. No se resuelve reintentando en un ciclo.
SII_PORTAL_UNEXPECTED_PAGEEl portal respondió una página que el SDK no pudo interpretar. Como las páginas de folios de una empresa inscrita todavía no se han visto, esto es lo que encontrarías si las páginas reales difieren de aquellas para las que se escribió el SDK: abre un issue en GitHub con una captura.
CAF_INVALIDEl archivo que devolvió el portal, o el que leíste, no es un CAF válido, o su llave privada no es el par de su clave pública.
TRANSPORT_FAILED, TRANSPORT_TLS, REQUEST_TIMEOUT, SII_HTTP_ERRORFalló la red o el SII. El cliente del portal reintenta las fallas de transporte, los timeouts, los HTTP 502, 503 y 504 y las fallas TLS intermitentes del SII; un límite de sesiones, un bloqueo o un rechazo nunca se reintenta, porque eso los empeora.

Para obtener una traza para el SII o para un issue, dale al cliente del portal capture: createDirectoryCapture({ dir }) de dte-sdk/node: escribe cada intercambio con los secretos tachados.

Siguientes pasos#

  • Emitir una factura para enviar la factura que acabas de emitir.
  • Portal para todo lo demás que hace el cliente del portal: anulación, el registro de compras y ventas y el respaldo MIPYME.
  • Certificación y su corredor, que obtiene sus folios con estas mismas funciones.
  • Seguridad para saber cómo guardar un CAF y un certificado.