Files
Horux360/docs/plans/2026-01-25-sat-sync-design.md
Consultoria AS b49902bcff docs: add SAT sync feature design
Design document for automatic CFDI synchronization with SAT:
- FIEL (e.firma) authentication
- Download emitted and received CFDIs
- Daily automated sync at 3:00 AM
- Initial extraction of last 10 years
- Encrypted credential storage (AES-256-GCM)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 00:35:12 +00:00

10 KiB

Diseño: Sincronización con SAT

Resumen

Implementar sincronización automática de CFDIs desde el portal del SAT usando la e.firma (FIEL).

Requisitos

Aspecto Decisión
Autenticación FIEL (archivos .cer y .key + contraseña)
Tipos de CFDI Emitidos y recibidos
Ejecución Programada diaria a las 3:00 AM
Almacenamiento credenciales Encriptadas en PostgreSQL (AES-256-GCM)
Primera extracción Últimos 10 años
Extracciones posteriores Solo mes actual
Duplicados Actualizar con versión del SAT

Arquitectura General

┌─────────────────┐     ┌──────────────────┐     ┌─────────────┐
│   Frontend      │────▶│   API Horux      │────▶│  SAT WSDL   │
│  (Configuración)│     │  (sat.service)   │     │  Web Service│
└─────────────────┘     └──────────────────┘     └─────────────┘
                               │
                               ▼
                        ┌──────────────┐
                        │  PostgreSQL  │
                        │  - fiel_credentials
                        │  - sat_sync_jobs
                        │  - cfdis
                        └──────────────┘

Integración con Web Services del SAT

Flujo de Descarga

1. AUTENTICACIÓN (Token válido por 5 minutos)
   - Crear timestamp (Created + Expires)
   - Generar digest SHA-1 del timestamp
   - Firmar digest con llave privada (.key) usando RSA-SHA1
   - Enviar SOAP con certificado (.cer) + firma
   - Recibir token SAML para usar en siguientes llamadas

2. SOLICITUD DE DESCARGA
   Parámetros:
   - RfcSolicitante: RFC de la empresa
   - FechaInicio: YYYY-MM-DDTHH:MM:SS
   - FechaFin: YYYY-MM-DDTHH:MM:SS
   - TipoSolicitud: "CFDI" o "Metadata"
   - TipoComprobante: "I"(ingreso), "E"(egreso), "T", "N", "P"
   - RfcEmisor / RfcReceptor: Filtrar por contraparte (opcional)
   
   Respuesta:
   - IdSolicitud: UUID para tracking
   - CodEstatus: 5000 = Aceptada

3. VERIFICACIÓN (Polling cada 30-60 segundos)
   Estados posibles:
   - 1: Aceptada (en proceso)
   - 2: En proceso
   - 3: Terminada (lista para descargar)
   - 4: Error
   - 5: Rechazada
   - 6: Vencida
   
   Respuesta exitosa incluye:
   - IdsPaquetes: Array de IDs de paquetes ZIP a descargar
   - NumeroCFDIs: Total de comprobantes encontrados

4. DESCARGA DE PAQUETES
   - Por cada IdPaquete, solicitar descarga
   - Respuesta: Paquete en Base64 (archivo ZIP)
   - Decodificar y extraer XMLs
   - Cada ZIP puede contener hasta 200,000 CFDIs

5. PROCESAMIENTO DE XMLs
   Por cada XML:
   - Parsear con @nodecfdi/cfdi-core
   - Extraer: UUID, emisor, receptor, total, impuestos, fecha
   - Buscar en BD por UUID
   - Si existe → UPDATE
   - Si no existe → INSERT
   - Guardar XML original

Endpoints del SAT

Servicio URL
Autenticación https://cfdidescargamasivasolicitud.clouda.sat.gob.mx/Autenticacion/Autenticacion.svc
Solicitud https://cfdidescargamasivasolicitud.clouda.sat.gob.mx/SolicitaDescargaService.svc
Verificación https://cfdidescargamasivasolicitud.clouda.sat.gob.mx/VerificaSolicitudDescargaService.svc
Descarga https://cfdidescargamasiva.clouda.sat.gob.mx/DescargaMasivaService.svc

Estructura SOAP para Autenticación

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
            xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
  <s:Header>
    <o:Security xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <u:Timestamp u:Id="_0">
        <u:Created>2026-01-25T00:00:00.000Z</u:Created>
        <u:Expires>2026-01-25T00:05:00.000Z</u:Expires>
      </u:Timestamp>
      <o:BinarySecurityToken 
        ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"
        EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
        u:Id="uuid-cert">
        <!-- Certificado .cer en Base64 -->
      </o:BinarySecurityToken>
      <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo>
          <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
          <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
          <Reference URI="#_0">
            <Transforms>
              <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            </Transforms>
            <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
            <DigestValue><!-- SHA1 del Timestamp --></DigestValue>
          </Reference>
        </SignedInfo>
        <SignatureValue><!-- Firma RSA-SHA1 --></SignatureValue>
        <KeyInfo>
          <o:SecurityTokenReference>
            <o:Reference URI="#uuid-cert"/>
          </o:SecurityTokenReference>
        </KeyInfo>
      </Signature>
    </o:Security>
  </s:Header>
  <s:Body/>
</s:Envelope>

Dependencias Node.js

{
  "@nodecfdi/credentials": "^2.0",
  "@nodecfdi/cfdi-core": "^0.5",
  "node-forge": "^1.3",
  "fast-xml-parser": "^4.0",
  "adm-zip": "^0.5",
  "node-cron": "^3.0"
}

Códigos de Error del SAT

Código Significado Acción
5000 Solicitud recibida Continuar con verificación
5002 Se agotó límite de solicitudes Esperar 24 horas
5004 No se encontraron CFDIs Registrar, no es error
5005 Solicitud duplicada Usar IdSolicitud existente
404 Paquete no encontrado Reintentar en 1 minuto
500 Error interno SAT Reintentar con backoff

Estrategia de Extracción Inicial (10 años)

  • Dividir en solicitudes mensuales (~121 solicitudes)
  • Procesar 3-4 meses por día para no saturar
  • Guardar progreso en sat_sync_jobs
  • Si falla, continuar desde último mes exitoso

Tiempos Estimados

Operación Tiempo
Autenticación 1-2 segundos
Solicitud aceptada 1-2 segundos
Verificación (paquete listo) 1-30 minutos
Descarga 10,000 CFDIs 30-60 segundos
Procesamiento 10,000 XMLs 2-5 minutos

Modelo de Datos

Nuevas Tablas (schema public)

-- Credenciales FIEL por tenant (encriptadas)
CREATE TABLE fiel_credentials (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  rfc VARCHAR(13) NOT NULL,
  cer_data BYTEA NOT NULL,
  key_data BYTEA NOT NULL,
  key_password_encrypted BYTEA NOT NULL,
  serial_number VARCHAR(50),
  valid_from TIMESTAMP NOT NULL,
  valid_until TIMESTAMP NOT NULL,
  is_active BOOLEAN DEFAULT true,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW(),
  UNIQUE(tenant_id)
);

-- Jobs de sincronización
CREATE TABLE sat_sync_jobs (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  type VARCHAR(20) NOT NULL,
  status VARCHAR(20) NOT NULL,
  date_from DATE NOT NULL,
  date_to DATE NOT NULL,
  cfdi_type VARCHAR(10),
  sat_request_id VARCHAR(50),
  sat_package_ids TEXT[],
  cfdis_found INTEGER DEFAULT 0,
  cfdis_downloaded INTEGER DEFAULT 0,
  cfdis_inserted INTEGER DEFAULT 0,
  cfdis_updated INTEGER DEFAULT 0,
  progress_percent INTEGER DEFAULT 0,
  error_message TEXT,
  started_at TIMESTAMP,
  completed_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW(),
  retry_count INTEGER DEFAULT 0,
  next_retry_at TIMESTAMP
);

CREATE INDEX idx_sat_sync_jobs_tenant ON sat_sync_jobs(tenant_id);
CREATE INDEX idx_sat_sync_jobs_status ON sat_sync_jobs(status);

Modificaciones a tabla cfdis

ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS source VARCHAR(20) DEFAULT 'manual';
ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS sat_sync_job_id UUID;
ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS xml_original TEXT;
ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS last_sat_sync TIMESTAMP;

Estructura de Archivos

apps/api/src/
├── services/
│   ├── sat/
│   │   ├── sat.service.ts
│   │   ├── sat-auth.service.ts
│   │   ├── sat-download.service.ts
│   │   ├── sat-parser.service.ts
│   │   └── sat-crypto.service.ts
│   └── fiel.service.ts
├── controllers/
│   ├── sat.controller.ts
│   └── fiel.controller.ts
├── routes/
│   ├── sat.routes.ts
│   └── fiel.routes.ts
└── jobs/
    └── sat-sync.job.ts

API Endpoints

POST   /api/fiel/upload          # Subir .cer, .key y contraseña
GET    /api/fiel/status          # Estado de FIEL configurada
DELETE /api/fiel                 # Eliminar credenciales

POST   /api/sat/sync             # Sincronización manual
GET    /api/sat/sync/status      # Estado actual
GET    /api/sat/sync/history     # Historial
GET    /api/sat/sync/:id         # Detalle de job
POST   /api/sat/sync/:id/retry   # Reintentar job fallido

Interfaz de Usuario

Sección en Configuración

  • Estado de FIEL (configurada/no configurada, vigencia)
  • Botones: Actualizar FIEL, Eliminar
  • Sincronización automática (frecuencia, última sync, total CFDIs)
  • Botón: Sincronizar Ahora
  • Historial de sincronizaciones (tabla)

Modal de Carga FIEL

  • Input para archivo .cer
  • Input para archivo .key
  • Input para contraseña
  • Mensaje de seguridad
  • Botones: Cancelar, Guardar y Validar

Notificaciones

Evento Mensaje
Sync completada "Se descargaron X CFDIs del SAT"
Sync fallida "Error al sincronizar: [mensaje]"
FIEL por vencer (30 días) "Tu e.firma vence el DD/MMM/YYYY"
FIEL vencida "Tu e.firma ha vencido"

Seguridad

  • Solo rol admin puede gestionar FIEL
  • Credenciales nunca se devuelven en API
  • Logs de auditoría para accesos
  • Rate limiting en endpoints de sincronización
  • Encriptación AES-256-GCM para credenciales