Skip to content

Integración GM Hades — Bóveda Fiscal

GM Hades Bóveda Fiscal es el microservicio interno de GM Transporte que expone acceso estructurado al repositorio SAT de la empresa mediante gRPC. Reemplaza a Prodigia como proveedor de CFDIs cuando la variable de entorno INVOICE_PROVIDER=hades. La integración con Hades está diseñada para procesar hasta 50,000 CFDIs por solicitud con paralelismo agresivo en cada fase.

Servicios gRPC expuestos por Hades

Hades expone tres servicios gRPC con responsabilidades diferenciadas:

ServicioDescripciónTimeout
BovedaFiscalDBService.ExecuteQueryConsulta UUIDs de CFDIs en la base MariaDB accedida por SSH tunnel2 minutos
BovedaFiscalSFTPService.DownloadBatchAsZipDescarga masiva de XMLs desde servidor SFTPSin límite explícito
QueryGatewayServiceProxy de consultas MSSQL multi-empresa al ERP de GM TransporteConfigurable

La conexión mantiene keepalive: ping cada 360 segundos, timeout de 20 segundos sin respuesta. La reconexión automática la gestiona el cliente gRPC sin intervención manual.

Configuración de conexión

Terminal window
# Desarrollo y QA
HADES_GRPC_ADDRESS=localhost:9456
HADES_GRPC_TLS=false
# Producción
HADES_GRPC_ADDRESS=hades.gmtransport.co:443
HADES_GRPC_TLS=true

El switch entre TLS y sin TLS no requiere cambios de código. El cliente gRPC en infrastructure/gateways/hades/client.go lee la variable HADES_GRPC_TLS al inicializar la conexión.

Flujo de descarga en seis fases

La obtención de facturas con Hades sigue seis fases secuenciales con paralelismo interno en cada una. El flujo completo está implementado en el adaptador del proveedor Hades.

flowchart TD
    A["Fase 1: Consulta de UUIDs\nBovedaFiscalDBService.ExecuteQuery\nRFC + período → lista de UUIDs"] --> B

    B["Fase 2: Filtro por caché local\nUUIDs en PostgreSQL 5434 o Azure Blob\n50 goroutines, chunks de 500"] --> C

    C{UUIDs faltantes?}
    C -->|No| G
    C -->|Sí| D

    D["Fase 3: Pre-enriquecimiento paralelo\n2 goroutines independientes (sync.WaitGroup)\nMetadatos MariaDB + Comprobantes relacionados\n4 chunks × 500 UUIDs en paralelo"] --> E

    E["Fase 4: Descarga SFTP en lotes\nBovedaFiscalSFTPService.DownloadBatchAsZip\n5 lotes simultáneos × 30 UUIDs\nStream de chunks 128 KB"] --> F

    F["Fase 5: Parseo XML paralelo\nRemoveNamespaces + xml.Unmarshal\n50 goroutines en paralelo"] --> G

    G["Fase 6: Almacenamiento caché dual\nMetadatos → PostgreSQL 5434\nXML → Azure Blob (SAS 1440 min)\nEvicción FIFO si > 1,000,000 entradas por RFC"]

Fase 1 — Consulta de UUIDs

BovedaFiscalDBService.ExecuteQuery recibe el RFC de la empresa y el rango de fechas del período. Consulta la base de datos MariaDB de la Bóveda Fiscal y devuelve la lista completa de UUIDs de CFDIs disponibles para ese RFC y período. El timeout es de 2 minutos.

Fase 2 — Filtro por caché local

Para cada UUID devuelto por Hades, el sistema verifica si ya existe en el caché local. Los UUIDs presentes en PostgreSQL 5434 o en Azure Blob se recuperan directamente con 50 goroutines procesando chunks de 500 entradas. Estos UUIDs no contactan el SAT nuevamente.

Fase 3 — Pre-enriquecimiento paralelo

Para los UUIDs faltantes en caché, dos goroutines independientes sincronizadas con sync.WaitGroup consultan simultáneamente la base de datos de la Bóveda Fiscal:

  • Goroutine A: metadatos de la factura (RFC emisor, RFC receptor, fecha, tipo).
  • Goroutine B: comprobantes relacionados y datos adicionales.

Cada goroutine procesa hasta 4 chunks de 500 UUIDs en paralelo, generando hasta 8 consultas DB simultáneas.

Fase 4 — Descarga SFTP en lotes

Los XMLs de los UUIDs faltantes se descargan mediante BovedaFiscalSFTPService.DownloadBatchAsZip. La descarga se organiza en lotes de 30 UUIDs con un semáforo de 5 lotes simultáneos (maxConcurrentBatches=5), lo que equivale a 150 UUIDs procesándose en paralelo en cada momento.

Internamente, Hades procesa cada lote con 12 stat workers y 8 download workers, empaqueta los resultados en un ZIP con manifest.json y los transmite como stream de chunks de 128 KB.

Fase 5 — Parseo XML paralelo

Los XMLs obtenidos del caché y del SFTP se parsean con 50 goroutines en paralelo. El flujo de parseo es obligatorio y no admite atajos:

// 1. Limpiar namespaces SAT antes de Unmarshal
xmlLimpio := xml_utils.RemoveNamespaces(xmlRaw)
// 2. Unmarshal al struct CFDI
var cfdi dtos.CFDI
if err := xml.Unmarshal([]byte(xmlLimpio), &cfdi); err != nil {
return fmt.Errorf("parsear CFDI: %w", err)
}
// 3. UUID siempre con EqualFold
if strings.EqualFold(cfdi.UUID, uuidBuscado) { ... }
// 4. Montos: parsear con TrimSpace, redondear inmediatamente
monto, _ := strconv.ParseFloat(strings.TrimSpace(cfdi.Total), 64)
montoRedondeado := mathUtils.RedondearMonto(monto)

Omitir RemoveNamespaces antes de xml.Unmarshal produce fallos silenciosos: el Unmarshal puede completarse sin error pero con campos vacíos si los prefijos de namespace SAT no coinciden con los esperados por las estructuras Go.

Fase 6 — Almacenamiento en caché dual

Los XMLs nuevos se almacenan en dos capas:

  • PostgreSQL 5434: metadatos del CFDI (UUID, RFC emisor, RFC receptor, tipo, fecha, totales).
  • Azure Blob Storage: contenido XML completo con URL SAS de 1440 minutos por defecto.

Si la cantidad de entradas por RFC supera el límite CACHE_MAX_ENTRIES_PER_RFC (por defecto 1,000,000), se aplica evicción FIFO hasta reducir al 50% del límite. Este proceso es automático y no requiere intervención.

Pre-conciliación autónoma

Con Hades como proveedor activo, el sistema puede ejecutar un pipeline de pre-conciliación en segundo plano inmediatamente después de crear una solicitud. El SATPreCacheAdapter orquesta dos goroutines sincronizadas con sync.WaitGroup:

flowchart LR
    A[Solicitud creada] --> B[sync.WaitGroup]
    B --> C["Goroutine A: Rama SAT\nDescargar CFDIs del período\nAlmacenar en caché dual"]
    B --> D["Goroutine B: Rama ERP\nObtener facturas del ERP\nAlmacenar en ERPCacheEntry"]
    C --> E[sync.WaitGroup.Done]
    D --> E
    E --> F["SolicitudAsset almacenado\nListo para conciliación instantánea"]

Cuando el contador inicia la conciliación explícitamente, el sistema detecta el SolicitudAsset precacheado y lo usa directamente, haciendo que el proceso de conciliación sea prácticamente instantáneo independientemente del volumen de CFDIs.

Comparación Prodigia vs Hades

AspectoProdigia (legado)Hades (moderno)
ProtocoloHTTP RESTgRPC (protobuf)
Caché localNoSí (PG 5434 + Azure Blob)
EscalaMiles de CFDIs50,000+ CFDIs
Pre-conciliaciónNoSí (autónoma)
Consulta ERPDirecta HTTPVia QueryGatewayService
TLSConfigurableConfigurable
ConfiguraciónINVOICE_PROVIDER=prodigiaINVOICE_PROVIDER=hades

El output final de ambos proveedores es equivalente: un ZIP con XMLs de CFDIs que el sistema procesa con las mismas utilidades (xml_utils.go, RemoveNamespaces, xml.Unmarshal). La diferencia está en el mecanismo de obtención, el caché y la escala.

Resiliencia de la integración

El cliente gRPC implementa varios mecanismos de resiliencia:

  • Keepalive: ping cada 360 segundos con timeout de 20 segundos. Detecta conexiones colgadas sin necesidad de reiniciar el proceso.
  • Reconexión automática: el cliente gRPC de Google gestiona el ciclo de reconexión por defecto.
  • Azure fallback: si Azure Blob no está disponible durante el almacenamiento en caché, el sistema cae hacia almacenamiento local sin interrumpir el flujo principal.
  • Evicción FIFO: el caché se autorregula al superar el límite por RFC, evitando que el almacenamiento crezca sin control.

Resumen

  • GM Hades expone tres servicios gRPC: consulta de UUIDs en MariaDB, descarga masiva por SFTP y proxy ERP multi-empresa.
  • El flujo de descarga tiene seis fases con paralelismo interno controlado por semáforos y goroutines: hasta 50 goroutines para parseo y caché, 5 lotes simultáneos de 30 UUIDs para SFTP.
  • RemoveNamespaces() antes de xml.Unmarshal es obligatorio; omitirlo produce fallos silenciosos en el parseo.
  • El caché dual (PostgreSQL 5434 + Azure Blob) evita reconsultas al SAT con evicción FIFO automática al superar 1,000,000 entradas por RFC.
  • La pre-conciliación autónoma descarga y cachea facturas SAT y ERP en segundo plano para hacer la conciliación casi instantánea.