Metadata-Version: 2.4
Name: py-vucem
Version: 0.1.2
Summary: Cliente en Python para la Ventanilla Única de Comercio Exterior Mexicano (VUCEM)
Project-URL: Homepage, https://github.com/pesatto/py-vucem
Project-URL: Repository, https://github.com/pesatto/py-vucem
Project-URL: Issues, https://github.com/pesatto/py-vucem/issues
Author-email: Fernando Ruiz <fernando.ruiz@pesatto.com>
License: MIT
Keywords: aduanas,comercio exterior,manifestacion de valor,mexico,soap,vucem
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Natural Language :: Spanish
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Office/Business
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Requires-Dist: cryptography>=42.0.0
Requires-Dist: lxml>=4.9.0
Requires-Dist: requests>=2.28.0
Requires-Dist: zeep>=4.2.1
Description-Content-Type: text/markdown

# py-vucem

Cliente Python para la **Ventanilla Única de Comercio Exterior Mexicano (VUCEM)**. Permite registrar Manifestaciones de Valor Electrónicas (MVE), digitalizar documentos, consultar pedimentos y generar el acuse de recepción en HTML, todo mediante los servicios SOAP oficiales de VUCEM firmados con e.firma (FIEL).

## Requisitos

- Python 3.9+
- e.firma (FIEL) vigente de la empresa importadora (archivos `.cer` y `.key`)
- Clave de acceso a servicios web de VUCEM (`claveWS`)
- Token de Banxico SIE (opcional, para tipo de cambio automático)

## Instalación

```bash
pip install py-vucem
```

O en modo desarrollo:

```bash
git clone https://github.com/pesatto/py_vucem
cd py_vucem
pip install -e .
```

## Configuración inicial

```python
from py_vucem import VucemClient

client = VucemClient(
    cer_path="ruta/a/fiel.cer",   # e.firma de la empresa (NO el CSD de facturación)
    key_path="ruta/a/fiel.key",
    password="tu-password-fiel",
    clave_ws="tu-clave-ws-vucem",
    rfc="TU_RFC",
)
```

> **Importante:** VUCEM requiere la **e.firma (FIEL)** de la empresa, no el CSD. Son archivos diferentes — el CSD es solo para firmar CFDIs.

---

## Servicios disponibles

### 1. Manifestación de Valor Electrónica (MVE)

La MVE documenta el valor declarado de las mercancías importadas (Art. 59-A Ley Aduanera).

#### Construir y enviar una MVE

```python
from py_vucem import VucemClient
from py_vucem.models.mv_builder import ManifestacionValor
from py_vucem.utils.banxico import BanxicoClient

banxico = BanxicoClient("tu-token-banxico")

client = VucemClient(
    cer_path="fiel.cer",
    key_path="fiel.key",
    password="password",
    clave_ws="clave_ws",
    rfc="PME241011C34",
)

# Construir el modelo
mv = ManifestacionValor("PME241011C34", banxico=banxico)

# Personas autorizadas a consultar la MV (agente aduanal)
mv.agregar_consulta("LWO041215F90", "TIPFIG.AGE")

# eDocuments relacionados (COVEs digitalizados en VUCEM)
mv.agregar_documento("03602601109K2")
mv.agregar_documento("0170261DD0RU6")

# Pedimento aduanal
mv.agregar_pedimento(pedimento="6003095", patente="3977", aduana="160")

# Factura / COVE
factura = mv.nueva_factura(
    cove="COVE2680TJDY6",
    incoterm="TIPINC.FOB",
    vinculacion=False,
    metodo="VALADU.VTM",
)

# Pagos realizados (TC se consulta automáticamente de Banxico)
factura.agregar_pago(
    fecha="2026-02-03", total=21328.00,
    forma="FORPAG.TE", moneda="USD",
)
factura.agregar_pago(
    fecha="2026-03-11", total=47613.00,
    forma="FORPAG.TE", moneda="USD",
)

# Incrementables al valor en aduana (fletes, seguros, etc.)
factura.agregar_incrementable(
    tipo="INCRE.GS", fecha="2026-03-21",
    total=1800.00, moneda="USD",
    cargo_importador=True,
)

# Enviar a VUCEM
resultado = client.registrar_manifestacion_desde_modelo(mv.to_dict())
print(resultado["numeroOperacion"])  # ej. 530502

# El XML firmado que se envió queda disponible en el resultado
Path("mv_firmado.xml").write_text(resultado["_xml"], encoding="utf-8")
```

#### Tipos de cambio

Si no configuras `BanxicoClient`, debes proporcionar `tc=` en cada pago:

```python
mv = ManifestacionValor("PME241011C34")  # sin banxico

factura.agregar_pago(
    fecha="2026-02-03", total=21328.00,
    forma="FORPAG.TE", moneda="USD",
    tc=20.1234,  # obligatorio sin BanxicoClient
)
```

Con `BanxicoClient`, el TC FIX oficial se consulta automáticamente por fecha y moneda. Si proporcionas `tc=` junto con `BanxicoClient`, valida que tu TC no difiera más del 1% del oficial.

#### Tipos de pago disponibles

| Método | Descripción |
|--------|-------------|
| `agregar_pago()` | Precio ya pagado |
| `agregar_por_pagar()` | Precio pendiente de pago (crédito) |
| `agregar_compenso()` | Pago por compensación (intercambio de bienes/servicios) |
| `agregar_incrementable()` | Fletes, seguros, cargos incrementables al valor |
| `agregar_decrementable()` | Descuentos, devoluciones, conceptos decrementables |

#### Obtener el XML

Hay dos momentos en los que puedes necesitar el XML:

```python
# 1. Antes de enviar — para revisar la estructura o guardarlo
xml_sin_firma = client.generar_xml_mv(mv.to_dict(), con_firma=False)  # placeholders de firma
xml_firmado   = client.generar_xml_mv(mv.to_dict(), con_firma=True)   # firma FIEL real

Path("mv_firmado.xml").write_text(xml_firmado, encoding="utf-8")

# Validar contra el XSD de VUCEM
errores = client.validar_xml_mv(xml_firmado)
if errores:
    for e in errores:
        print("Error XSD:", e)

# 2. Al registrar — el XML firmado queda en resultado["_xml"]
resultado = client.registrar_manifestacion_desde_xml(xml_firmado)
print(resultado["numeroOperacion"])   # ej. 530502
Path("mv_enviada.xml").write_text(resultado["_xml"], encoding="utf-8")
```

> Guarda siempre `resultado["_xml"]` junto con el `numeroOperacion`. Puedes necesitarlo como respaldo o para auditoría.

#### Generar el pre-acuse (borrador antes de enviar)

Genera el HTML del acuse con los datos del XML, **sin conectarse a VUCEM**. Útil para verificar que incoterms, montos y pedimentos estén correctos antes de registrar.

```python
xml = client.generar_xml_mv(mv.to_dict(), con_firma=True)

html = client.generar_preacuse(xml, nombre_importador="PESATTO MEXICO, SA DE CV")
Path("borrador_mv.html").write_text(html, encoding="utf-8")
# Abrir en el navegador para revisar → Ctrl+P para obtener PDF
```

Los campos que dependen de VUCEM (eDocument, sellos, cadena original) aparecen como marcadores con la etiqueta **BORRADOR**. El RFC y tipo de figura de consulta se extraen directamente del XML.

#### Consultar una MVE registrada

```python
# Por número de operación
data = client.consultar_manifestacion(numero_operacion=530502)

# Por número de MV (MNVA...)
data = client.consultar_manifestacion(edocument="MNVA2600GAOE4")

print(data["estatus"])       # "Aceptado"
print(data["eDocument"])     # "MNVA2600GAOE4"
print(data["fechaRegistro"]) # "2026-06-29T..."
```

---

### 2. Acuse de recepción de MVE

VUCEM no genera un acuse PDF automáticamente para las MVEs registradas por API — el documento debe generarse del lado del cliente con los datos del trámite.

#### Generar el acuse (después de registrar en VUCEM)

Solo necesitas el número de operación. El cliente realiza internamente dos consultas:
1. Por `numero_operacion` → obtiene eDocument + sellos + cadena original
2. Por eDocument → obtiene `datosManifestacionValor` (todos los datos de la MV)

Si el estatus no es "Aceptado", lanza `ValueError`.

```python
html = client.generar_acuse(
    numero_operacion=530502,
    nombre_importador="PESATTO MEXICO, SA DE CV",
)
Path("acuse_mv.html").write_text(html, encoding="utf-8")
# Abrir en navegador → Ctrl+P → Guardar como PDF
```

#### Obtener PDF

El HTML incluye CSS `@media print` optimizado — el encabezado se repite en cada página y los bloques no se cortan. Para obtener PDF:

1. Abre el `.html` en Chrome o Edge
2. Ctrl+P → **Destino: Guardar como PDF**
3. Sin márgenes, orientación vertical, papel Carta

#### Múltiples COVEs

Si la MVE tiene más de un COVE, el documento genera **una página por COVE**. Cada página incluye la información del COVE, sus pagos, incrementables, valor en aduana y su acuse de recepción individual.

```python
# Con dos facturas/COVEs
factura1 = mv.nueva_factura(cove="COVE26XXXXXX1", ...)
factura2 = mv.nueva_factura(cove="COVE26XXXXXX2", ...)

# El HTML generado tendrá 2 páginas (una por COVE)
html = client.generar_acuse(numero_operacion=530502, nombre_importador="...")
```

---

### 3. Digitalización de documentos

Convierte documentos PDF en eDocuments registrados en VUCEM (facturas, listas de empaque, certificados, etc.).

```python
# Ver catálogo de tipos de documento disponibles
tipos = client.consultar_tipos_documento()

# Digitalizar un PDF
with open("factura.pdf", "rb") as f:
    pdf_bytes = f.read()

resultado = client.digitalizar_documento(
    correo="contacto@empresa.com",
    id_tipo_documento="1",       # 1 = Factura comercial
    nombre_documento="Factura ST2601016",
    archivo_bytes=pdf_bytes,
    rfc_consulta="PME241011C34", # RFC autorizado a consultar
)

print(resultado["eDocument"])    # ej. "03602601109K2"
print(resultado["numeroOperacion"])

# Consultar el estatus de la digitalización
estatus = client.consultar_estatus_digitalizacion(resultado["numeroOperacion"])
```

> El PDF debe pesar menos de 3 MB y ser un PDF válido (no escaneado como imagen sin OCR).

---

### 4. Consulta de COVEs

```python
# Consultar un COVE por su eDocument
cove = client.consultar_cove("COVE2680TJDY6")
print(cove)
```

---

### 5. Consulta de pedimentos

```python
# Listar pedimentos por aduana y fecha
pedimentos = client.listar_pedimentos(
    aduana="160",
    patente=3977,
    fecha_inicio=date(2026, 1, 1),
    fecha_fin=date(2026, 6, 30),
)

# Consultar estado de un pedimento
estado = client.consultar_estado_pedimentos(
    aduana=160, patente=3977,
    pedimento=6003095,
    numero_operacion=530502,
)

# Detalle completo del pedimento
detalle = client.consultar_pedimento_completo(
    aduana="160", patente=3977, pedimento=6003095,
)

# Consultar una partida específica
partida = client.consultar_partida(
    aduana="160", patente=3977, pedimento=6003095,
    numero_operacion=530502, numero_partida=1,
)

# Consultar remesas
remesas = client.consultar_remesas(
    aduana=160, patente=3977,
    pedimento=6003095,
    numero_operacion=530502,
)
```

---

### 6. Descarga de acuses PDF (eDocuments y COVEs)

```python
# Acuse de un eDocument digitalizado
resultado = client.descargar_acuse("03602601109K2")

if resultado.get("pdf"):
    with open("acuse.pdf", "wb") as f:
        f.write(resultado["pdf"])

# Acuse de un COVE
resultado = client.descargar_acuse("COVE2680TJDY6", es_cove=True)
```

> El servicio de acuses (`ConsultaAcusesServiceWS`) soporta eDocuments con formato numérico y COVEs. Los números MVE (MNVA...) no son compatibles con este servicio — para las MVE usa `client.generar_acuse(numero_operacion)`.

---

## Banco de México — Tipo de cambio

```python
from py_vucem.utils.banxico import BanxicoClient

banxico = BanxicoClient("tu-token")

# TC FIX para una fecha específica
tc = banxico.tc("USD", "2026-02-03")
# Decimal('17.2532')

# Monedas soportadas
# USD, EUR, GBP, JPY, CAD, CHF, CNY
```

Token gratuito en: `https://www.banxico.org.mx/SieAPIRest/service/v1/token`

El TC FIX de Banxico es el mismo que usa el SAT para convertir montos en moneda extranjera en pedimentos y MVE (Art. 20 Ley Aduanera).

---

## Claves de catálogos VUCEM

### Incoterms (`incoterm`)
| Clave | Código |
|-------|--------|
| `TIPINC.FOB` | FOB |
| `TIPINC.CIF` | CIF |
| `TIPINC.CFR` | CFR |
| `TIPINC.EXW` | EXW |
| `TIPINC.DAP` | DAP |
| `TIPINC.DDP` | DDP |
| `TIPINC.CPT` | CPT |
| `TIPINC.FCA` | FCA |

### Formas de pago (`forma`)
| Clave | Descripción |
|-------|-------------|
| `FORPAG.TE` | Transferencia electrónica |
| `FORPAG.CH` | Cheque |
| `FORPAG.EF` | Efectivo |
| `FORPAG.LC` | Letra de cambio |
| `FORPAG.CC` | Carta de crédito |
| `FORPAG.OT` | Otro (requiere `especifique=`) |

### Incrementables (`tipo`)
| Clave | Descripción |
|-------|-------------|
| `INCRE.GS` | Gastos de transporte, seguros y conexos |
| `INCRE.CG` | Comisiones y gastos de corretaje |
| `INCRE.CE` | Costo de envases o embalajes |
| `INCRE.GT` | Gastos de embalaje |
| `INCRE.MP` | Materiales, piezas y partes |
| `INCRE.HM` | Herramientas, matrices y moldes |
| `INCRE.TI` | Trabajos de ingeniería y diseño |
| `INCRE.RD` | Regalías y derechos de licencia |

### Decrementables (`tipo`)
| Clave | Descripción |
|-------|-------------|
| `DECRE.GR` | Gastos realizados por cuenta del importador |
| `DECRE.RP` | Gastos posteriores a la importación |
| `DECRE.GT` | Transporte posteriores al punto de internación |
| `DECRE.RC` | Contribuciones y cuotas compensatorias |

### Métodos de valoración (`metodo`)
| Clave | Descripción |
|-------|-------------|
| `VALADU.VTM` | Valor de transacción de mercancías |
| `VALADU.VMI` | Valor de transacción de mercancías idénticas |
| `VALADU.VMS` | Valor de transacción de mercancías similares |
| `VALADU.VPU` | Valor de precio unitario de venta |
| `VALADU.VR`  | Valor reconstruido |
| `VALADU.A78` | Valor conforme al Art. 78 LA |

### Tipo de figura (`figura` en `agregar_consulta`)
| Clave | Descripción |
|-------|-------------|
| `TIPFIG.AGE` | Agente aduanal |
| `TIPFIG.AAD` | Agencia aduanal |
| `TIPFIG.REP` | Representante legal |
| `TIPFIG.IMP` | Importador |

---

## Scripts de ejemplo

Los scripts en `ejemplos/` muestran flujos completos listos para ejecutar:

| Script | Descripción |
|--------|-------------|
| `ejemplos/enviar_mv_6003095.py` | Construye, valida y envía una MVE completa |
| `ejemplos/generar_preacuse_6003095.py` | Genera el borrador del acuse (sin VUCEM) para revisar antes de enviar |
| `ejemplos/generar_acuse_6003095.py` | Genera el acuse oficial con sellos a partir del número de operación |
| `ejemplos/consultar_mv_530488.py` | Consulta el estatus de una MVE |
| `ejemplos/cargar_documentos_6003095.py` | Digitaliza documentos PDF en VUCEM |
| `ejemplos/crear_pedimento_6003095.py` | Construye el modelo de MVE en JSON para revisión |

Flujo típico:

```bash
cd py_vucem

# 1. Revisar datos antes de enviar
.\venv\Scripts\python.exe ejemplos\generar_preacuse_6003095.py

# 2. Enviar a VUCEM (guarda el XML y el numeroOperacion)
.\venv\Scripts\python.exe ejemplos\enviar_mv_6003095.py

# 3. Generar el acuse oficial
.\venv\Scripts\python.exe ejemplos\generar_acuse_6003095.py
```

---

## Notas técnicas

### Conectividad con VUCEM

Los servicios de VUCEM requieren configuración SSL especial (certificados legacy, TLS 1.0/1.1). La librería maneja esto internamente mediante `LegacyVucemAdapter` — no es necesaria ninguna configuración adicional.

### WSDL remoto vs local

La librería usa WSDLs remotos cuando el servidor los sirve correctamente. Para servicios donde el XSD remoto devuelve 404 (como `ConsultaManifestacionService`), usa WSDLs locales empaquetados solo para validación, mientras la comunicación sigue siendo contra el endpoint en producción.

### Cadena original y firma

La cadena original de la MVE usa el mismo formato numérico que el XML (máximo 3 decimales redondeados, sin ceros finales), garantizando que la firma electrónica sea válida. Este formato es equivalente al `formatVucemNumber()` de la implementación PHP de referencia de VUCEM.

### Prerequisito para acuses de MVE

El acuse de una MVE solo se genera cuando el COVE referenciado existe y está validado en VUCEM. El COVE debe ser registrado por el agente aduanal **antes** de presentar la MVE.

### Generación de PDF desde el acuse HTML

El HTML del acuse incluye CSS `@media print` optimizado. Para convertir a PDF:

1. Abre el `.html` en **Chrome o Edge**
2. `Ctrl+P` → Destino: **Guardar como PDF**
3. Configura: sin márgenes, orientación vertical, papel Carta

El encabezado se repite automáticamente en cada página impresa gracias al uso de `<thead>` en la tabla principal — no requiere ninguna librería adicional.

---

## Estructura del proyecto

```
py_vucem/
├── src/py_vucem/
│   ├── client.py                  # VucemClient — punto de entrada principal
│   ├── models/
│   │   ├── mv_builder.py          # ManifestacionValor y Factura (API fluida)
│   │   ├── mv_mapper.py           # Convierte modelo a estructura interna
│   │   ├── mv_xml.py              # Generador de XML SOAP firmado
│   │   ├── mv_xml_parser.py       # Parser inverso: XML VUCEM → modelo
│   │   └── mv_acuse_generator.py  # Generador de acuse HTML
│   ├── services/
│   │   ├── manifestacion_service.py   # Registro, consulta y acuses de MVE
│   │   ├── digitalizar_documento.py   # Digitalización de PDFs
│   │   ├── pedimentos_service.py      # Consulta de pedimentos aduaneros
│   │   └── cove_consulta.py           # Consulta de COVEs
│   └── utils/
│       ├── __init__.py            # FielHandler, LegacyVucemAdapter
│       └── banxico.py             # BanxicoClient — TC FIX oficial
├── ejemplos/                      # Scripts listos para ejecutar
├── tests/                         # Pruebas unitarias
│   └── fixtures/                  # Certificados de prueba
└── wsdl/                          # WSDLs locales de respaldo
```

---

## Licencia

MIT © Fernando Ruiz — [Pesatto México](https://pesatto.com)
