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:
| Servicio | Descripción | Timeout |
|---|---|---|
BovedaFiscalDBService.ExecuteQuery | Consulta UUIDs de CFDIs en la base MariaDB accedida por SSH tunnel | 2 minutos |
BovedaFiscalSFTPService.DownloadBatchAsZip | Descarga masiva de XMLs desde servidor SFTP | Sin límite explícito |
QueryGatewayService | Proxy de consultas MSSQL multi-empresa al ERP de GM Transporte | Configurable |
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
# Desarrollo y QAHADES_GRPC_ADDRESS=localhost:9456HADES_GRPC_TLS=false
# ProducciónHADES_GRPC_ADDRESS=hades.gmtransport.co:443HADES_GRPC_TLS=trueEl 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 UnmarshalxmlLimpio := xml_utils.RemoveNamespaces(xmlRaw)
// 2. Unmarshal al struct CFDIvar cfdi dtos.CFDIif err := xml.Unmarshal([]byte(xmlLimpio), &cfdi); err != nil { return fmt.Errorf("parsear CFDI: %w", err)}
// 3. UUID siempre con EqualFoldif strings.EqualFold(cfdi.UUID, uuidBuscado) { ... }
// 4. Montos: parsear con TrimSpace, redondear inmediatamentemonto, _ := 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
| Aspecto | Prodigia (legado) | Hades (moderno) |
|---|---|---|
| Protocolo | HTTP REST | gRPC (protobuf) |
| Caché local | No | Sí (PG 5434 + Azure Blob) |
| Escala | Miles de CFDIs | 50,000+ CFDIs |
| Pre-conciliación | No | Sí (autónoma) |
| Consulta ERP | Directa HTTP | Via QueryGatewayService |
| TLS | Configurable | Configurable |
| Configuración | INVOICE_PROVIDER=prodigia | INVOICE_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 dexml.Unmarshales 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.