# Implementación de Sincronización SAT ## Resumen Sistema de sincronización automática de CFDIs con el SAT (Servicio de Administración Tributaria de México) para Horux360. ## Componentes Implementados ### 1. Backend (API) #### Servicios | Archivo | Descripción | |---------|-------------| | `src/services/fiel.service.ts` | Gestión de credenciales FIEL (e.firma) | | `src/services/sat/sat-client.service.ts` | Cliente para el servicio web del SAT | | `src/services/sat/sat.service.ts` | Lógica principal de sincronización | | `src/services/sat/sat-crypto.service.ts` | Encriptación AES-256-GCM para credenciales | | `src/services/sat/sat-parser.service.ts` | Parser de XMLs de CFDI | #### Controladores | Archivo | Descripción | |---------|-------------| | `src/controllers/fiel.controller.ts` | Endpoints para gestión de FIEL | | `src/controllers/sat.controller.ts` | Endpoints para sincronización SAT | #### Job Programado | Archivo | Descripción | |---------|-------------| | `src/jobs/sat-sync.job.ts` | Cron job para sincronización diaria (3:00 AM) | ### 2. Frontend (Web) #### Componentes | Archivo | Descripción | |---------|-------------| | `components/sat/FielUploadModal.tsx` | Modal para subir certificado y llave FIEL | | `components/sat/SyncStatus.tsx` | Estado de sincronización con selector de fechas | | `components/sat/SyncHistory.tsx` | Historial de sincronizaciones | #### Página | Archivo | Descripción | |---------|-------------| | `app/(dashboard)/configuracion/sat/page.tsx` | Página de configuración SAT | ### 3. Base de Datos #### Tabla Principal (schema public) ```sql -- sat_sync_jobs: Almacena los trabajos de sincronización CREATE TABLE sat_sync_jobs ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL, type VARCHAR(20) NOT NULL, -- 'initial' | 'daily' status VARCHAR(20) NOT NULL, -- 'pending' | 'running' | 'completed' | 'failed' date_from TIMESTAMP NOT NULL, date_to TIMESTAMP NOT NULL, cfdi_type VARCHAR(20), sat_request_id VARCHAR(100), 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 ); -- fiel_credentials: Almacena las credenciales FIEL encriptadas CREATE TABLE fiel_credentials ( id UUID PRIMARY KEY, tenant_id UUID UNIQUE NOT NULL, rfc VARCHAR(13) NOT NULL, cer_data BYTEA NOT NULL, key_data BYTEA NOT NULL, key_password_encrypted BYTEA NOT NULL, encryption_iv BYTEA NOT NULL, encryption_tag BYTEA NOT NULL, serial_number VARCHAR(100), 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() ); ``` #### Columnas agregadas a tabla cfdis (por tenant) ```sql ALTER TABLE tenant_xxx.cfdis ADD COLUMN xml_original TEXT; ALTER TABLE tenant_xxx.cfdis ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP; ALTER TABLE tenant_xxx.cfdis ADD COLUMN last_sat_sync TIMESTAMP; ALTER TABLE tenant_xxx.cfdis ADD COLUMN sat_sync_job_id UUID; ALTER TABLE tenant_xxx.cfdis ADD COLUMN source VARCHAR(20) DEFAULT 'manual'; ``` ## Dependencias ```json { "@nodecfdi/sat-ws-descarga-masiva": "^2.0.0", "@nodecfdi/credentials": "^2.0.0", "@nodecfdi/cfdi-core": "^1.0.1" } ``` ## Flujo de Sincronización ``` 1. Usuario configura FIEL (certificado .cer + llave .key + contraseña) ↓ 2. Sistema valida y encripta credenciales (AES-256-GCM) ↓ 3. Usuario inicia sincronización (manual o automática 3:00 AM) ↓ 4. Sistema desencripta FIEL y crea cliente SAT ↓ 5. Por cada mes en el rango: a. Solicitar CFDIs emitidos al SAT b. Esperar respuesta (polling cada 30s) c. Descargar paquetes ZIP d. Extraer y parsear XMLs e. Guardar en BD del tenant f. Repetir para CFDIs recibidos ↓ 6. Marcar job como completado ``` ## API Endpoints ### FIEL | Método | Ruta | Descripción | |--------|------|-------------| | GET | `/api/fiel/status` | Estado de la FIEL configurada | | POST | `/api/fiel/upload` | Subir nueva FIEL | | DELETE | `/api/fiel` | Eliminar FIEL | ### Sincronización SAT | Método | Ruta | Descripción | |--------|------|-------------| | POST | `/api/sat/sync` | Iniciar sincronización | | GET | `/api/sat/sync/status` | Estado actual | | GET | `/api/sat/sync/history` | Historial de syncs | | GET | `/api/sat/sync/:id` | Detalle de un job | | POST | `/api/sat/sync/:id/retry` | Reintentar job fallido | ### Parámetros de sincronización ```typescript interface StartSyncRequest { type?: 'initial' | 'daily'; // default: 'daily' dateFrom?: string; // ISO date, ej: "2025-01-01T00:00:00" dateTo?: string; // ISO date, ej: "2025-12-31T23:59:59" } ``` ## Configuración ### Variables de entorno ```env # Clave para encriptar credenciales FIEL (32 bytes hex) FIEL_ENCRYPTION_KEY=tu_clave_de_32_bytes_en_hexadecimal # Zona horaria para el cron TZ=America/Mexico_City ``` ### Límites del SAT - **Antigüedad máxima**: 6 años - **Solicitudes por día**: Limitadas (se reinicia cada 24h) - **Tamaño de paquete**: Variable ## Errores Comunes del SAT | Código | Mensaje | Solución | |--------|---------|----------| | 5000 | Solicitud Aceptada | OK - esperar verificación | | 5002 | Límite de solicitudes agotado | Esperar 24 horas | | 5004 | No se encontraron CFDIs | Normal si no hay facturas en el rango | | 5005 | Solicitud duplicada | Ya existe una solicitud pendiente | | - | Información mayor a 6 años | Ajustar rango de fechas | | - | No se permite descarga de cancelados | Facturas canceladas no disponibles | ## Seguridad 1. **Encriptación de credenciales**: AES-256-GCM con IV único 2. **Almacenamiento seguro**: Certificado, llave y contraseña encriptados 3. **Autenticación**: JWT con tenantId embebido 4. **Aislamiento**: Cada tenant tiene su propio schema en PostgreSQL ## Servicios Systemd ```bash # API Backend systemctl status horux-api # Web Frontend systemctl status horux-web ``` ## Comandos Útiles ```bash # Ver logs de sincronización SAT journalctl -u horux-api -f | grep "\[SAT\]" # Estado de jobs psql -U postgres -d horux360 -c "SELECT * FROM sat_sync_jobs ORDER BY created_at DESC LIMIT 5;" # CFDIs sincronizados por tenant psql -U postgres -d horux360 -c "SELECT COUNT(*) FROM tenant_xxx.cfdis WHERE source = 'sat';" ``` ## Changelog ### 2026-01-25 - Implementación inicial de sincronización SAT - Integración con librería @nodecfdi/sat-ws-descarga-masiva - Soporte para fechas personalizadas en sincronización - Corrección de cast UUID en queries SQL - Agregadas columnas faltantes a tabla cfdis - UI para selección de periodo personalizado - Cambio de servicio web a modo producción (next start) ## Estado Actual (2026-01-25) ### Completado - [x] Servicio de encriptación de credenciales FIEL - [x] Integración con @nodecfdi/sat-ws-descarga-masiva - [x] Parser de XMLs de CFDI - [x] UI para subir FIEL - [x] UI para ver estado de sincronización - [x] UI para seleccionar periodo personalizado - [x] Cron job para sincronización diaria (3:00 AM) - [x] Soporte para fechas personalizadas - [x] Corrección de cast UUID en queries - [x] Columnas adicionales en tabla cfdis de todos los tenants ### Pendiente por probar El SAT bloqueó las solicitudes por exceso de pruebas. **Esperar 24 horas** y luego: 1. Ir a **Configuración > SAT** 2. Clic en **"Periodo personalizado"** 3. Seleccionar: **2025-01-01** a **2025-12-31** 4. Clic en **"Sincronizar periodo"** ### Tenant de prueba - **RFC**: CAS2408138W2 - **Schema**: `tenant_cas2408138w2` - **Nota**: Los CFDIs "recibidos" de este tenant están cancelados (SAT no permite descargarlos) ### Comandos para verificar después de 24h ```bash # Ver estado del sync PGPASSWORD=postgres psql -h localhost -U postgres -d horux360 -c \ "SELECT status, cfdis_found, cfdis_downloaded, cfdis_inserted FROM sat_sync_jobs ORDER BY created_at DESC LIMIT 1;" # Ver logs en tiempo real journalctl -u horux-api -f | grep "\[SAT\]" # Contar CFDIs sincronizados PGPASSWORD=postgres psql -h localhost -U postgres -d horux360 -c \ "SELECT COUNT(*) as total FROM tenant_cas2408138w2.cfdis WHERE source = 'sat';" ``` ### Problemas conocidos 1. **"Se han agotado las solicitudes de por vida"**: Límite de SAT alcanzado, esperar 24h 2. **"No se permite la descarga de xml que se encuentren cancelados"**: Normal para facturas canceladas 3. **"Información mayor a 6 años"**: SAT solo permite descargar últimos 6 años ## Próximos Pasos - [ ] Probar sincronización completa después de 24h - [ ] Verificar que los CFDIs se guarden correctamente - [ ] Implementar reintentos automáticos para errores temporales - [ ] Notificaciones por email al completar sincronización - [ ] Dashboard con estadísticas de CFDIs por periodo - [ ] Soporte para filtros adicionales (RFC emisor/receptor, tipo de comprobante)