From b49902bcff64fe7ba21d5f0891bc588bdb9cc871 Mon Sep 17 00:00:00 2001 From: Consultoria AS Date: Sun, 25 Jan 2026 00:35:12 +0000 Subject: [PATCH] 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 --- docs/plans/2026-01-25-sat-sync-design.md | 327 +++++++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 docs/plans/2026-01-25-sat-sync-design.md diff --git a/docs/plans/2026-01-25-sat-sync-design.md b/docs/plans/2026-01-25-sat-sync-design.md new file mode 100644 index 0000000..284f2cf --- /dev/null +++ b/docs/plans/2026-01-25-sat-sync-design.md @@ -0,0 +1,327 @@ +# 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 +