# 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
<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

```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

