Skip to content

Commit 569959a

Browse files
authored
Merge pull request #6 from FiscalAPI/feat/comercio-exterior
v4.0.386 complemento-comercio-exterior added
2 parents 18a6a1e + f6b2b09 commit 569959a

12 files changed

Lines changed: 2630 additions & 3 deletions

examples/ejemplos-factura-comercio-exterior-referencias.py

Lines changed: 1182 additions & 0 deletions
Large diffs are not rendered by default.

examples/ejemplos-factura-comercio-exterior-valores.py

Lines changed: 1133 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""
2+
Ejemplo: firma de carta manifiesto vía POST /api/v4/manifests usando el SDK de FiscalAPI.
3+
4+
El endpoint recibe el certificado (.cer) y la llave privada (.key) de la FIEL (e.firma)
5+
del contribuyente en base64, además de la contraseña de la llave. Devuelve el PDF de la
6+
carta manifiesto firmada (también en base64).
7+
8+
Importante: usar FIEL, NO CSD. El payload es idéntico al de tax-files, pero el endpoint
9+
requiere los archivos de la e.firma del SAT (no los del CSD de timbrado).
10+
"""
11+
from fiscalapi import FiscalApiClient, FiscalApiSettings
12+
from fiscalapi.models import SignManifestRequest
13+
14+
15+
# Configuración de FiscalAPI
16+
settings = FiscalApiSettings(
17+
api_url="https://test.fiscalapi.com",
18+
api_key="API_KEY",
19+
tenant="TENANT_ID"
20+
)
21+
22+
# Credenciales FIEL de prueba (ESCUELA KEMPER URGATE)
23+
escuela_kemper_urgate_base64_cer_fiel = "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";
24+
escuela_kemper_urgate_base64_key_fiel = "MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIAgEAAoIBAQACAggAMBQGCCqGSIb3DQMHBAgwggS9AgEAMASCBMh4EHl7aNSCaMDA1VlRoXCZ5UUmqErAbucRBAKNQXH8t0f2aSYkk74BKjNDtQ2u5LylXUf6MrOHJZV3J/U8IVU02MuKmvSb5kIvEKE3FZ/OSKoZC58sLQ/+m1joBEHn2QgMld3NsSRA6rv5Jv7Gyl8URb3XT6W7qr/nznufyuxyBSTj68+HgdJt/EeGp3Ud1Sk/1mY8PEt7tT86UjH+20/07ZVIhik6fQDFxELrm4jCJkTrQenqEykHVy/w5mJgrqwfS4SsGaNY9J5TUJNauyw193y/6C48SxwpTjA2GYtpxIUl9GNtXbUHUvsbVTZ06gnnCXUx3IOj+wFI9qUvPYl2DVSfV3iVAYsTXG/R5gOIw4k6s235gZEfJpoQZ4eq1LiYbKXqTjR9ntCEit0RyWirwt/2UcrRNgnXFU58xYUzY45noSR6QD8u05ZGsGT1Q3xesanSxCmsjrfh26a5EoH3voFhpI99/M2CyhwIm63ab2CKz8DMrhCPWxUgKDn2xZgW79zBgQ6qN0t9iqJ69guwG4rkWNqs/5vyYdK5PbJSt9KmeHZNYl1/wBMP4PDomA0iHiRQ2Jpc7tdedQXPid3DSIrk9XqMwB6SBThGOSzJ4Tkb78ybsHTjx3apF4ZHH6U66WzRbjggt2H0XmCbKLvNbAPqfA+vZzpIZK0LrsUGvG/wtoqFedZ/AV3wBvQTIRkwHImvFqoDd7fMCg8MLV3yJJGNcgcsI1RSV0EAJ5YafNHR9IPNl6pI7X6RDT7jobeyelyE5xepJklnNCIGLkYTGmXgXG0cYxtWYqvT7jQuEeseFSlxScxbvWI1vTzRpuis2YiZYteX4f+kGnMq6eOw+qtqXwB2bsw48ZGC9Ar34znqX/iOogQD+I2zWfllEdhQWdBF11m5YmwfnfQyoOOhx5s2bdqWv+XN6BMtRxfG7UOvB0p9ki1FdGOBD9BsSaF0etF8HPmy9zHPugCRQH50eVbqNnxPAnyaucPR/8ZEJXVuQ99KkhZRwr5brR9WP6ooDnws+yMe3q95hVVXWd4zDWPuh2JcT4ZzVzm9iwB7EZDYYCq/UXTg07Bb8hL0pw7UJFDelNMr4HOsarJZyUZferGQ62Ki7d/9xGOF4aL/lmni5okXU+fUKWkT7UhJCcxr3zeCnbe5FTNZgjIbieAMPtJiS30ow1EtxUsQF32f14lDdsi8twTT0GsILvZwNvGk/KZRlQU2iLm4hr44w6gmyg1J7m5ivL/MwbQ/DIf4djcOqckWBqtLCAp/HwSYjIXxrlPF1pICnzbfxTc6gLyTOsvoCngSnP2feOMTMMpFXVC1h0mfObxOhcbwxCC18AnNqfxh5rCvNxKYi0yqa5g9UFBOOKyxrhvP/eMdnPpd1DtvzP79zMQCQ+NlXt/XtmDRNUpPc7nPwhdPbRJTsetDqZK9NQf6cZ4/2cDIMd1/QomTmzKU9cjNXZgMcDSY7UYrN+n8CrETwu9dgNDuYjynh4XYlm0x9/Rx5r+77d1nzE6rkBH17/lH3fb8p4MWDlr6HIIgXxYCmeAhiws8tthJoD/nk1n6fvrxTFrEmEE1XG7JrNrDP1dLnjH+paHXy0thdQ8lpBMM4Wtqk0KVPn2SyaY3dslkdHg=";
25+
password = "12345678a"
26+
27+
28+
# ============================================================================
29+
# 1. FIRMA DE CARTA MANIFIESTO (ESCUELA KEMPER URGATE)
30+
# ============================================================================
31+
def firma_carta_manifiesto(client: FiscalApiClient) -> None:
32+
request = SignManifestRequest(
33+
base64_cer=escuela_kemper_urgate_base64_cer_fiel,
34+
base64_key=escuela_kemper_urgate_base64_key_fiel,
35+
password=password,
36+
)
37+
response = client.manifests.sign(request)
38+
print("Response:", response)
39+
40+
41+
# ============================================================================
42+
# FUNCIÓN PRINCIPAL
43+
# ============================================================================
44+
def main() -> None:
45+
client = FiscalApiClient(settings=settings)
46+
try:
47+
48+
firma_carta_manifiesto(client)
49+
pass
50+
except Exception as error:
51+
print("Error:", error)
52+
53+
54+
if __name__ == "__main__":
55+
main()

fiscalapi/__init__.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,26 @@
4747
CartaPorteComplement,
4848
)
4949

50+
# Modelos de Comercio Exterior
51+
from .models.comercio_exterior_models import (
52+
ComercioExteriorEmisorDomicilio,
53+
ComercioExteriorReceptorDomicilio,
54+
ComercioExteriorDestinatarioDomicilio,
55+
ComercioExteriorEmisor,
56+
ComercioExteriorPropietario,
57+
ComercioExteriorReceptor,
58+
ComercioExteriorDestinatario,
59+
ComercioExteriorMercanciaDescripcionEspecifica,
60+
ComercioExteriorMercancia,
61+
ComercioExteriorComplement,
62+
)
63+
64+
# Modelos de firma de manifiestos
65+
from .models.manifest_models import (
66+
SignManifestRequest,
67+
SignManifestResponse,
68+
)
69+
5070
# Modelos de dominio
5171
from .models.fiscalapi_models import (
5272
# Product models
@@ -141,6 +161,7 @@
141161
from .services.product_service import ProductService
142162
from .services.tax_file_service import TaxFileService
143163
from .services.stamp_service import StampService
164+
from .services.manifest_service import ManifestService
144165

145166
# Cliente principal
146167
from .services.fiscalapi_client import FiscalApiClient
@@ -219,6 +240,17 @@
219240
"ParteTransporte",
220241
"TipoFigura",
221242
"CartaPorteComplement",
243+
# Comercio Exterior models
244+
"ComercioExteriorEmisorDomicilio",
245+
"ComercioExteriorReceptorDomicilio",
246+
"ComercioExteriorDestinatarioDomicilio",
247+
"ComercioExteriorEmisor",
248+
"ComercioExteriorPropietario",
249+
"ComercioExteriorReceptor",
250+
"ComercioExteriorDestinatario",
251+
"ComercioExteriorMercanciaDescripcionEspecifica",
252+
"ComercioExteriorMercancia",
253+
"ComercioExteriorComplement",
222254
# Invoice models
223255
"InvoiceResponse",
224256
"Invoice",
@@ -251,6 +283,9 @@
251283
"UserLookupDto",
252284
"StampTransaction",
253285
"StampTransactionParams",
286+
# Manifest models
287+
"SignManifestRequest",
288+
"SignManifestResponse",
254289
# Servicios
255290
"BaseService",
256291
"ApiKeyService",
@@ -265,6 +300,7 @@
265300
"ProductService",
266301
"TaxFileService",
267302
"StampService",
303+
"ManifestService",
268304
# Cliente principal
269305
"FiscalApiClient",
270306
]

fiscalapi/models/__init__.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,24 @@
3333
TipoFigura,
3434
CartaPorteComplement,
3535
)
36+
from .comercio_exterior_models import (
37+
# Comercio Exterior models
38+
ComercioExteriorEmisorDomicilio,
39+
ComercioExteriorReceptorDomicilio,
40+
ComercioExteriorDestinatarioDomicilio,
41+
ComercioExteriorEmisor,
42+
ComercioExteriorPropietario,
43+
ComercioExteriorReceptor,
44+
ComercioExteriorDestinatario,
45+
ComercioExteriorMercanciaDescripcionEspecifica,
46+
ComercioExteriorMercancia,
47+
ComercioExteriorComplement,
48+
)
49+
from .manifest_models import (
50+
# Manifest models
51+
SignManifestRequest,
52+
SignManifestResponse,
53+
)
3654
from .fiscalapi_models import (
3755
# Product models
3856
ProductTax,
@@ -137,6 +155,17 @@
137155
"ParteTransporte",
138156
"TipoFigura",
139157
"CartaPorteComplement",
158+
# Comercio Exterior models
159+
"ComercioExteriorEmisorDomicilio",
160+
"ComercioExteriorReceptorDomicilio",
161+
"ComercioExteriorDestinatarioDomicilio",
162+
"ComercioExteriorEmisor",
163+
"ComercioExteriorPropietario",
164+
"ComercioExteriorReceptor",
165+
"ComercioExteriorDestinatario",
166+
"ComercioExteriorMercanciaDescripcionEspecifica",
167+
"ComercioExteriorMercancia",
168+
"ComercioExteriorComplement",
140169
"BaseDto",
141170
"CatalogDto",
142171
"FiscalApiSettings",
@@ -218,4 +247,7 @@
218247
"UserLookupDto",
219248
"StampTransaction",
220249
"StampTransactionParams",
250+
# Manifest models
251+
"SignManifestRequest",
252+
"SignManifestResponse",
221253
]
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
"""Modelos del complemento Comercio Exterior para CFDI 4.0."""
2+
3+
from decimal import Decimal
4+
from typing import Optional
5+
6+
from pydantic import BaseModel, ConfigDict, Field
7+
8+
9+
# ===== Domicilios =====
10+
11+
class ComercioExteriorEmisorDomicilio(BaseModel):
12+
"""Domicilio del emisor del complemento Comercio Exterior."""
13+
calle: str = Field(default=..., alias="calle")
14+
numero_exterior: Optional[str] = Field(default=None, alias="numeroExterior")
15+
numero_interior: Optional[str] = Field(default=None, alias="numeroInterior")
16+
colonia_id: Optional[str] = Field(default=None, alias="coloniaId")
17+
localidad_id: Optional[str] = Field(default=None, alias="localidadId")
18+
referencia: Optional[str] = Field(default=None, alias="referencia")
19+
municipio_id: Optional[str] = Field(default=None, alias="municipioId")
20+
estado_id: str = Field(default=..., alias="estadoId")
21+
pais_id: str = Field(default=..., alias="paisId")
22+
codigo_postal_id: str = Field(default=..., alias="codigoPostalId")
23+
24+
model_config = ConfigDict(populate_by_name=True)
25+
26+
27+
class ComercioExteriorReceptorDomicilio(BaseModel):
28+
"""Domicilio del receptor del complemento Comercio Exterior."""
29+
calle: str = Field(default=..., alias="calle")
30+
numero_exterior: Optional[str] = Field(default=None, alias="numeroExterior")
31+
numero_interior: Optional[str] = Field(default=None, alias="numeroInterior")
32+
colonia: Optional[str] = Field(default=None, alias="colonia")
33+
localidad: Optional[str] = Field(default=None, alias="localidad")
34+
referencia: Optional[str] = Field(default=None, alias="referencia")
35+
municipio: Optional[str] = Field(default=None, alias="municipio")
36+
estado: str = Field(default=..., alias="estado")
37+
pais_id: str = Field(default=..., alias="paisId")
38+
codigo_postal: str = Field(default=..., alias="codigoPostal")
39+
40+
model_config = ConfigDict(populate_by_name=True)
41+
42+
43+
class ComercioExteriorDestinatarioDomicilio(BaseModel):
44+
"""Domicilio de un destinatario del complemento Comercio Exterior."""
45+
calle: str = Field(default=..., alias="calle")
46+
numero_exterior: Optional[str] = Field(default=None, alias="numeroExterior")
47+
numero_interior: Optional[str] = Field(default=None, alias="numeroInterior")
48+
colonia: Optional[str] = Field(default=None, alias="colonia")
49+
localidad: Optional[str] = Field(default=None, alias="localidad")
50+
referencia: Optional[str] = Field(default=None, alias="referencia")
51+
municipio: Optional[str] = Field(default=None, alias="municipio")
52+
estado: str = Field(default=..., alias="estado")
53+
pais_id: str = Field(default=..., alias="paisId")
54+
codigo_postal: str = Field(default=..., alias="codigoPostal")
55+
56+
model_config = ConfigDict(populate_by_name=True)
57+
58+
59+
# ===== Emisor =====
60+
61+
class ComercioExteriorEmisor(BaseModel):
62+
"""Emisor del complemento Comercio Exterior."""
63+
curp: Optional[str] = Field(default=None, alias="curp")
64+
domicilio: ComercioExteriorEmisorDomicilio = Field(default=..., alias="domicilio")
65+
66+
model_config = ConfigDict(populate_by_name=True)
67+
68+
69+
# ===== Propietario =====
70+
71+
class ComercioExteriorPropietario(BaseModel):
72+
"""Propietario de las mercancías exportadas."""
73+
num_reg_id_trib: str = Field(default=..., alias="numRegIdTrib")
74+
residencia_fiscal_id: str = Field(default=..., alias="residenciaFiscalId")
75+
76+
model_config = ConfigDict(populate_by_name=True)
77+
78+
79+
# ===== Receptor =====
80+
81+
class ComercioExteriorReceptor(BaseModel):
82+
"""Receptor del complemento Comercio Exterior."""
83+
num_reg_id_trib: Optional[str] = Field(default=None, alias="numRegIdTrib")
84+
domicilio: Optional[ComercioExteriorReceptorDomicilio] = Field(default=None, alias="domicilio")
85+
86+
model_config = ConfigDict(populate_by_name=True)
87+
88+
89+
# ===== Destinatario =====
90+
91+
class ComercioExteriorDestinatario(BaseModel):
92+
"""Destinatario de las mercancías exportadas."""
93+
num_reg_id_trib: Optional[str] = Field(default=None, alias="numRegIdTrib")
94+
nombre: Optional[str] = Field(default=None, alias="nombre")
95+
domicilios: list[ComercioExteriorDestinatarioDomicilio] = Field(default_factory=list, alias="domicilios")
96+
97+
model_config = ConfigDict(populate_by_name=True)
98+
99+
100+
# ===== Mercancías =====
101+
102+
class ComercioExteriorMercanciaDescripcionEspecifica(BaseModel):
103+
"""Descripción específica de una mercancía del complemento Comercio Exterior."""
104+
marca: str = Field(default=..., alias="marca")
105+
modelo: Optional[str] = Field(default=None, alias="modelo")
106+
sub_modelo: Optional[str] = Field(default=None, alias="subModelo")
107+
numero_serie: Optional[str] = Field(default=None, alias="numeroSerie")
108+
109+
model_config = ConfigDict(populate_by_name=True)
110+
111+
112+
class ComercioExteriorMercancia(BaseModel):
113+
"""Mercancía del complemento Comercio Exterior."""
114+
no_identificacion: str = Field(default=..., alias="noIdentificacion")
115+
fraccion_arancelaria_id: Optional[str] = Field(default=None, alias="fraccionArancelariaId")
116+
cantidad_aduana: Optional[Decimal] = Field(default=None, alias="cantidadAduana")
117+
unidad_aduana_id: Optional[str] = Field(default=None, alias="unidadAduanaId")
118+
valor_unitario_aduana: Optional[Decimal] = Field(default=None, alias="valorUnitarioAduana")
119+
valor_dolares: Decimal = Field(default=..., alias="valorDolares")
120+
descripciones_especificas: Optional[list[ComercioExteriorMercanciaDescripcionEspecifica]] = Field(
121+
default=None, alias="descripcionesEspecificas"
122+
)
123+
124+
model_config = ConfigDict(populate_by_name=True, json_encoders={Decimal: str})
125+
126+
127+
# ===== Comercio Exterior =====
128+
129+
class ComercioExteriorComplement(BaseModel):
130+
"""Complemento Comercio Exterior para exportación de mercancías en CFDI 4.0."""
131+
motivo_traslado_id: Optional[str] = Field(default=None, alias="motivoTrasladoId")
132+
clave_de_pedimento_id: str = Field(default=..., alias="claveDePedimentoId")
133+
certificado_origen: int = Field(default=..., alias="certificadoOrigen")
134+
num_certificado_origen: Optional[str] = Field(default=None, alias="numCertificadoOrigen")
135+
numero_exportador_confiable: Optional[str] = Field(default=None, alias="numeroExportadorConfiable")
136+
incoterm_id: Optional[str] = Field(default=None, alias="incotermId")
137+
observaciones: Optional[str] = Field(default=None, alias="observaciones")
138+
tipo_cambio_usd: Decimal = Field(default=..., alias="tipoCambioUSD")
139+
emisor: Optional[ComercioExteriorEmisor] = Field(default=None, alias="emisor")
140+
receptor: Optional[ComercioExteriorReceptor] = Field(default=None, alias="receptor")
141+
propietarios: Optional[list[ComercioExteriorPropietario]] = Field(default=None, alias="propietarios")
142+
destinatarios: Optional[list[ComercioExteriorDestinatario]] = Field(default=None, alias="destinatarios")
143+
mercancias: list[ComercioExteriorMercancia] = Field(default_factory=list, alias="mercancias")
144+
145+
model_config = ConfigDict(populate_by_name=True, json_encoders={Decimal: str})

fiscalapi/models/fiscalapi_models.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from pydantic import BaseModel, ConfigDict, EmailStr, Field
33
from fiscalapi.models.common_models import BaseDto, CatalogDto
44
from fiscalapi.models.carta_porte_models import CartaPorteComplement
5+
from fiscalapi.models.comercio_exterior_models import ComercioExteriorComplement
56
from typing import Literal, Optional
67
from datetime import datetime
78

@@ -74,7 +75,9 @@ class Person(BaseDto):
7475
committed_balance: Optional[Decimal] = Field(default=None, alias="committedBalance", description="Saldo en tránsito.")
7576
tenant_id: Optional[str] = Field(default=None, alias="tenantId", description="ID del tenant al que pertenece el emisor.")
7677
tenant: Optional[CatalogDto] = Field(default=None, alias="tenant", description="Tenant expandido.")
77-
78+
country_id: Optional[str] = Field(default=None, alias="countryId", description="Código del país de residencia para extranjeros (catálogo c_Pais).")
79+
foreign_tin: Optional[str] = Field(default=None, alias="foreignTin", description="Número de identificación fiscal del extranjero.")
80+
7881
model_config = ConfigDict(
7982
populate_by_name=True,
8083
json_encoders={Decimal: str}
@@ -218,6 +221,7 @@ class InvoiceRecipient(BaseDto):
218221
cfdi_use_code: Optional[str] = Field(default=None, alias="cfdiUseCode", description="Código del uso CFDI.")
219222
email: Optional[str] = Field(default=None, description="Correo electrónico del receptor.")
220223
foreign_country_code: Optional[str] = Field(default=None, alias="foreignCountryCode", description="Código del país de residencia para extranjeros.")
224+
country_id: Optional[str] = Field(default=None, alias="countryId", description="Código del país de residencia para extranjeros (catálogo c_Pais).")
221225
foreign_tin: Optional[str] = Field(default=None, alias="foreignTin", description="Número de identificación fiscal del extranjero.")
222226
employee_data: Optional[InvoiceRecipientEmployeeData] = Field(default=None, alias="employeeData", description="Datos del empleado para CFDI de nómina.")
223227

@@ -529,6 +533,7 @@ class InvoiceComplement(BaseDto):
529533
payment: Optional[PaymentComplement] = Field(default=None, alias="payment", description="Complemento de pago.")
530534
payroll: Optional[PayrollComplement] = Field(default=None, alias="payroll", description="Complemento de nómina.")
531535
carta_porte: Optional[CartaPorteComplement] = Field(default=None, alias="cartaPorte", description="Complemento carta porte.")
536+
comercio_exterior: Optional[ComercioExteriorComplement] = Field(default=None, alias="comercioExterior", description="Complemento comercio exterior.")
532537

533538
model_config = ConfigDict(populate_by_name=True)
534539

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Modelos para firma de carta manifiesto (endpoint POST /api/v4/manifests)."""
2+
3+
from typing import Optional
4+
from pydantic import BaseModel, ConfigDict, Field
5+
6+
7+
class SignManifestRequest(BaseModel):
8+
"""Petición para firmar una carta manifiesto con la FIEL del contribuyente."""
9+
10+
base64_cer: str = Field(default=..., alias="base64Cer", description="Certificado FIEL (.cer) en base64.")
11+
base64_key: str = Field(default=..., alias="base64Key", description="Llave privada FIEL (.key) en base64.")
12+
password: str = Field(default=..., alias="password", description="Contraseña de la llave privada FIEL.")
13+
14+
model_config = ConfigDict(populate_by_name=True)
15+
16+
17+
class SignManifestResponse(BaseModel):
18+
"""Respuesta con el PDF firmado de la carta manifiesto."""
19+
20+
base64_file: Optional[str] = Field(default=None, alias="base64File", description="PDF firmado en base64.")
21+
file_name: Optional[str] = Field(default=None, alias="fileName", description="Nombre sugerido del archivo (RFC.pdf).")
22+
file_extension: Optional[str] = Field(default=None, alias="fileExtension", description="Extensión del archivo (.pdf).")
23+
24+
model_config = ConfigDict(populate_by_name=True)

0 commit comments

Comments
 (0)