Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,182 changes: 1,182 additions & 0 deletions examples/ejemplos-factura-comercio-exterior-referencias.py

Large diffs are not rendered by default.

1,133 changes: 1,133 additions & 0 deletions examples/ejemplos-factura-comercio-exterior-valores.py

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions examples/ejemplos-firma-manifiestos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
Ejemplo: firma de carta manifiesto vía POST /api/v4/manifests usando el SDK de FiscalAPI.

El endpoint recibe el certificado (.cer) y la llave privada (.key) de la FIEL (e.firma)
del contribuyente en base64, además de la contraseña de la llave. Devuelve el PDF de la
carta manifiesto firmada (también en base64).

Importante: usar FIEL, NO CSD. El payload es idéntico al de tax-files, pero el endpoint
requiere los archivos de la e.firma del SAT (no los del CSD de timbrado).
"""
from fiscalapi import FiscalApiClient, FiscalApiSettings
from fiscalapi.models import SignManifestRequest


# Configuración de FiscalAPI
settings = FiscalApiSettings(
api_url="https://test.fiscalapi.com",
api_key="API_KEY",
tenant="TENANT_ID"
)

# Credenciales FIEL de prueba (ESCUELA KEMPER URGATE)
escuela_kemper_urgate_base64_cer_fiel = "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";
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=";
password = "12345678a"


# ============================================================================
# 1. FIRMA DE CARTA MANIFIESTO (ESCUELA KEMPER URGATE)
# ============================================================================
def firma_carta_manifiesto(client: FiscalApiClient) -> None:
request = SignManifestRequest(
base64_cer=escuela_kemper_urgate_base64_cer_fiel,
base64_key=escuela_kemper_urgate_base64_key_fiel,
password=password,
)
response = client.manifests.sign(request)
print("Response:", response)


# ============================================================================
# FUNCIÓN PRINCIPAL
# ============================================================================
def main() -> None:
client = FiscalApiClient(settings=settings)
try:

firma_carta_manifiesto(client)
pass
except Exception as error:
print("Error:", error)


if __name__ == "__main__":
main()
36 changes: 36 additions & 0 deletions fiscalapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,26 @@
CartaPorteComplement,
)

# Modelos de Comercio Exterior
from .models.comercio_exterior_models import (
ComercioExteriorEmisorDomicilio,
ComercioExteriorReceptorDomicilio,
ComercioExteriorDestinatarioDomicilio,
ComercioExteriorEmisor,
ComercioExteriorPropietario,
ComercioExteriorReceptor,
ComercioExteriorDestinatario,
ComercioExteriorMercanciaDescripcionEspecifica,
ComercioExteriorMercancia,
ComercioExteriorComplement,
)

# Modelos de firma de manifiestos
from .models.manifest_models import (
SignManifestRequest,
SignManifestResponse,
)

# Modelos de dominio
from .models.fiscalapi_models import (
# Product models
Expand Down Expand Up @@ -141,6 +161,7 @@
from .services.product_service import ProductService
from .services.tax_file_service import TaxFileService
from .services.stamp_service import StampService
from .services.manifest_service import ManifestService

# Cliente principal
from .services.fiscalapi_client import FiscalApiClient
Expand Down Expand Up @@ -219,6 +240,17 @@
"ParteTransporte",
"TipoFigura",
"CartaPorteComplement",
# Comercio Exterior models
"ComercioExteriorEmisorDomicilio",
"ComercioExteriorReceptorDomicilio",
"ComercioExteriorDestinatarioDomicilio",
"ComercioExteriorEmisor",
"ComercioExteriorPropietario",
"ComercioExteriorReceptor",
"ComercioExteriorDestinatario",
"ComercioExteriorMercanciaDescripcionEspecifica",
"ComercioExteriorMercancia",
"ComercioExteriorComplement",
# Invoice models
"InvoiceResponse",
"Invoice",
Expand Down Expand Up @@ -251,6 +283,9 @@
"UserLookupDto",
"StampTransaction",
"StampTransactionParams",
# Manifest models
"SignManifestRequest",
"SignManifestResponse",
# Servicios
"BaseService",
"ApiKeyService",
Expand All @@ -265,6 +300,7 @@
"ProductService",
"TaxFileService",
"StampService",
"ManifestService",
# Cliente principal
"FiscalApiClient",
]
32 changes: 32 additions & 0 deletions fiscalapi/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,24 @@
TipoFigura,
CartaPorteComplement,
)
from .comercio_exterior_models import (
# Comercio Exterior models
ComercioExteriorEmisorDomicilio,
ComercioExteriorReceptorDomicilio,
ComercioExteriorDestinatarioDomicilio,
ComercioExteriorEmisor,
ComercioExteriorPropietario,
ComercioExteriorReceptor,
ComercioExteriorDestinatario,
ComercioExteriorMercanciaDescripcionEspecifica,
ComercioExteriorMercancia,
ComercioExteriorComplement,
)
from .manifest_models import (
# Manifest models
SignManifestRequest,
SignManifestResponse,
)
from .fiscalapi_models import (
# Product models
ProductTax,
Expand Down Expand Up @@ -137,6 +155,17 @@
"ParteTransporte",
"TipoFigura",
"CartaPorteComplement",
# Comercio Exterior models
"ComercioExteriorEmisorDomicilio",
"ComercioExteriorReceptorDomicilio",
"ComercioExteriorDestinatarioDomicilio",
"ComercioExteriorEmisor",
"ComercioExteriorPropietario",
"ComercioExteriorReceptor",
"ComercioExteriorDestinatario",
"ComercioExteriorMercanciaDescripcionEspecifica",
"ComercioExteriorMercancia",
"ComercioExteriorComplement",
"BaseDto",
"CatalogDto",
"FiscalApiSettings",
Expand Down Expand Up @@ -218,4 +247,7 @@
"UserLookupDto",
"StampTransaction",
"StampTransactionParams",
# Manifest models
"SignManifestRequest",
"SignManifestResponse",
]
145 changes: 145 additions & 0 deletions fiscalapi/models/comercio_exterior_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
"""Modelos del complemento Comercio Exterior para CFDI 4.0."""

from decimal import Decimal
from typing import Optional

from pydantic import BaseModel, ConfigDict, Field


# ===== Domicilios =====

class ComercioExteriorEmisorDomicilio(BaseModel):
"""Domicilio del emisor del complemento Comercio Exterior."""
calle: str = Field(default=..., alias="calle")
numero_exterior: Optional[str] = Field(default=None, alias="numeroExterior")
numero_interior: Optional[str] = Field(default=None, alias="numeroInterior")
colonia_id: Optional[str] = Field(default=None, alias="coloniaId")
localidad_id: Optional[str] = Field(default=None, alias="localidadId")
referencia: Optional[str] = Field(default=None, alias="referencia")
municipio_id: Optional[str] = Field(default=None, alias="municipioId")
estado_id: str = Field(default=..., alias="estadoId")
pais_id: str = Field(default=..., alias="paisId")
codigo_postal_id: str = Field(default=..., alias="codigoPostalId")

model_config = ConfigDict(populate_by_name=True)


class ComercioExteriorReceptorDomicilio(BaseModel):
"""Domicilio del receptor del complemento Comercio Exterior."""
calle: str = Field(default=..., alias="calle")
numero_exterior: Optional[str] = Field(default=None, alias="numeroExterior")
numero_interior: Optional[str] = Field(default=None, alias="numeroInterior")
colonia: Optional[str] = Field(default=None, alias="colonia")
localidad: Optional[str] = Field(default=None, alias="localidad")
referencia: Optional[str] = Field(default=None, alias="referencia")
municipio: Optional[str] = Field(default=None, alias="municipio")
estado: str = Field(default=..., alias="estado")
pais_id: str = Field(default=..., alias="paisId")
codigo_postal: str = Field(default=..., alias="codigoPostal")

model_config = ConfigDict(populate_by_name=True)


class ComercioExteriorDestinatarioDomicilio(BaseModel):
"""Domicilio de un destinatario del complemento Comercio Exterior."""
calle: str = Field(default=..., alias="calle")
numero_exterior: Optional[str] = Field(default=None, alias="numeroExterior")
numero_interior: Optional[str] = Field(default=None, alias="numeroInterior")
colonia: Optional[str] = Field(default=None, alias="colonia")
localidad: Optional[str] = Field(default=None, alias="localidad")
referencia: Optional[str] = Field(default=None, alias="referencia")
municipio: Optional[str] = Field(default=None, alias="municipio")
estado: str = Field(default=..., alias="estado")
pais_id: str = Field(default=..., alias="paisId")
codigo_postal: str = Field(default=..., alias="codigoPostal")

model_config = ConfigDict(populate_by_name=True)


# ===== Emisor =====

class ComercioExteriorEmisor(BaseModel):
"""Emisor del complemento Comercio Exterior."""
curp: Optional[str] = Field(default=None, alias="curp")
domicilio: ComercioExteriorEmisorDomicilio = Field(default=..., alias="domicilio")

model_config = ConfigDict(populate_by_name=True)


# ===== Propietario =====

class ComercioExteriorPropietario(BaseModel):
"""Propietario de las mercancías exportadas."""
num_reg_id_trib: str = Field(default=..., alias="numRegIdTrib")
residencia_fiscal_id: str = Field(default=..., alias="residenciaFiscalId")

model_config = ConfigDict(populate_by_name=True)


# ===== Receptor =====

class ComercioExteriorReceptor(BaseModel):
"""Receptor del complemento Comercio Exterior."""
num_reg_id_trib: Optional[str] = Field(default=None, alias="numRegIdTrib")
domicilio: Optional[ComercioExteriorReceptorDomicilio] = Field(default=None, alias="domicilio")

model_config = ConfigDict(populate_by_name=True)


# ===== Destinatario =====

class ComercioExteriorDestinatario(BaseModel):
"""Destinatario de las mercancías exportadas."""
num_reg_id_trib: Optional[str] = Field(default=None, alias="numRegIdTrib")
nombre: Optional[str] = Field(default=None, alias="nombre")
domicilios: list[ComercioExteriorDestinatarioDomicilio] = Field(default_factory=list, alias="domicilios")

model_config = ConfigDict(populate_by_name=True)


# ===== Mercancías =====

class ComercioExteriorMercanciaDescripcionEspecifica(BaseModel):
"""Descripción específica de una mercancía del complemento Comercio Exterior."""
marca: str = Field(default=..., alias="marca")
modelo: Optional[str] = Field(default=None, alias="modelo")
sub_modelo: Optional[str] = Field(default=None, alias="subModelo")
numero_serie: Optional[str] = Field(default=None, alias="numeroSerie")

model_config = ConfigDict(populate_by_name=True)


class ComercioExteriorMercancia(BaseModel):
"""Mercancía del complemento Comercio Exterior."""
no_identificacion: str = Field(default=..., alias="noIdentificacion")
fraccion_arancelaria_id: Optional[str] = Field(default=None, alias="fraccionArancelariaId")
cantidad_aduana: Optional[Decimal] = Field(default=None, alias="cantidadAduana")
unidad_aduana_id: Optional[str] = Field(default=None, alias="unidadAduanaId")
valor_unitario_aduana: Optional[Decimal] = Field(default=None, alias="valorUnitarioAduana")
valor_dolares: Decimal = Field(default=..., alias="valorDolares")
descripciones_especificas: Optional[list[ComercioExteriorMercanciaDescripcionEspecifica]] = Field(
default=None, alias="descripcionesEspecificas"
)

model_config = ConfigDict(populate_by_name=True, json_encoders={Decimal: str})


# ===== Comercio Exterior =====

class ComercioExteriorComplement(BaseModel):
"""Complemento Comercio Exterior para exportación de mercancías en CFDI 4.0."""
motivo_traslado_id: Optional[str] = Field(default=None, alias="motivoTrasladoId")
clave_de_pedimento_id: str = Field(default=..., alias="claveDePedimentoId")
certificado_origen: int = Field(default=..., alias="certificadoOrigen")
num_certificado_origen: Optional[str] = Field(default=None, alias="numCertificadoOrigen")
numero_exportador_confiable: Optional[str] = Field(default=None, alias="numeroExportadorConfiable")
incoterm_id: Optional[str] = Field(default=None, alias="incotermId")
observaciones: Optional[str] = Field(default=None, alias="observaciones")
tipo_cambio_usd: Decimal = Field(default=..., alias="tipoCambioUSD")
emisor: Optional[ComercioExteriorEmisor] = Field(default=None, alias="emisor")
receptor: Optional[ComercioExteriorReceptor] = Field(default=None, alias="receptor")
propietarios: Optional[list[ComercioExteriorPropietario]] = Field(default=None, alias="propietarios")
destinatarios: Optional[list[ComercioExteriorDestinatario]] = Field(default=None, alias="destinatarios")
mercancias: list[ComercioExteriorMercancia] = Field(default_factory=list, alias="mercancias")

model_config = ConfigDict(populate_by_name=True, json_encoders={Decimal: str})
7 changes: 6 additions & 1 deletion fiscalapi/models/fiscalapi_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from pydantic import BaseModel, ConfigDict, EmailStr, Field
from fiscalapi.models.common_models import BaseDto, CatalogDto
from fiscalapi.models.carta_porte_models import CartaPorteComplement
from fiscalapi.models.comercio_exterior_models import ComercioExteriorComplement
from typing import Literal, Optional
from datetime import datetime

Expand Down Expand Up @@ -74,7 +75,9 @@ class Person(BaseDto):
committed_balance: Optional[Decimal] = Field(default=None, alias="committedBalance", description="Saldo en tránsito.")
tenant_id: Optional[str] = Field(default=None, alias="tenantId", description="ID del tenant al que pertenece el emisor.")
tenant: Optional[CatalogDto] = Field(default=None, alias="tenant", description="Tenant expandido.")

country_id: Optional[str] = Field(default=None, alias="countryId", description="Código del país de residencia para extranjeros (catálogo c_Pais).")
foreign_tin: Optional[str] = Field(default=None, alias="foreignTin", description="Número de identificación fiscal del extranjero.")

model_config = ConfigDict(
populate_by_name=True,
json_encoders={Decimal: str}
Expand Down Expand Up @@ -218,6 +221,7 @@ class InvoiceRecipient(BaseDto):
cfdi_use_code: Optional[str] = Field(default=None, alias="cfdiUseCode", description="Código del uso CFDI.")
email: Optional[str] = Field(default=None, description="Correo electrónico del receptor.")
foreign_country_code: Optional[str] = Field(default=None, alias="foreignCountryCode", description="Código del país de residencia para extranjeros.")
country_id: Optional[str] = Field(default=None, alias="countryId", description="Código del país de residencia para extranjeros (catálogo c_Pais).")
foreign_tin: Optional[str] = Field(default=None, alias="foreignTin", description="Número de identificación fiscal del extranjero.")
employee_data: Optional[InvoiceRecipientEmployeeData] = Field(default=None, alias="employeeData", description="Datos del empleado para CFDI de nómina.")

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

model_config = ConfigDict(populate_by_name=True)

Expand Down
24 changes: 24 additions & 0 deletions fiscalapi/models/manifest_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Modelos para firma de carta manifiesto (endpoint POST /api/v4/manifests)."""

from typing import Optional
from pydantic import BaseModel, ConfigDict, Field


class SignManifestRequest(BaseModel):
"""Petición para firmar una carta manifiesto con la FIEL del contribuyente."""

base64_cer: str = Field(default=..., alias="base64Cer", description="Certificado FIEL (.cer) en base64.")
base64_key: str = Field(default=..., alias="base64Key", description="Llave privada FIEL (.key) en base64.")
password: str = Field(default=..., alias="password", description="Contraseña de la llave privada FIEL.")

model_config = ConfigDict(populate_by_name=True)


class SignManifestResponse(BaseModel):
"""Respuesta con el PDF firmado de la carta manifiesto."""

base64_file: Optional[str] = Field(default=None, alias="base64File", description="PDF firmado en base64.")
file_name: Optional[str] = Field(default=None, alias="fileName", description="Nombre sugerido del archivo (RFC.pdf).")
file_extension: Optional[str] = Field(default=None, alias="fileExtension", description="Extensión del archivo (.pdf).")

model_config = ConfigDict(populate_by_name=True)
Loading