# 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 ```xml 2026-01-25T00:00:00.000Z 2026-01-25T00:05:00.000Z ``` ### Dependencias Node.js ```json { "@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) ```sql -- 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 ```sql 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