fiscalapi 4.0.386__tar.gz → 4.0.417__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {fiscalapi-4.0.386/fiscalapi.egg-info → fiscalapi-4.0.417}/PKG-INFO +231 -9
  2. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/README.md +230 -8
  3. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/__init__.py +26 -2
  4. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/models/__init__.py +30 -7
  5. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/models/carta_porte_models.py +12 -12
  6. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/models/comercio_exterior_models.py +2 -2
  7. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/models/common_models.py +1 -5
  8. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/models/fiscalapi_models.py +73 -89
  9. fiscalapi-4.0.417/fiscalapi/models/sat_validation_models.py +96 -0
  10. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/services/__init__.py +3 -1
  11. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/services/base_service.py +10 -8
  12. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/services/fiscalapi_client.py +2 -0
  13. fiscalapi-4.0.417/fiscalapi/services/sat_validation_service.py +90 -0
  14. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/services/stamp_service.py +9 -2
  15. {fiscalapi-4.0.386 → fiscalapi-4.0.417/fiscalapi.egg-info}/PKG-INFO +231 -9
  16. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi.egg-info/SOURCES.txt +2 -0
  17. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/setup.py +1 -1
  18. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/LICENSE.txt +0 -0
  19. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/MANIFEST.in +0 -0
  20. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/models/manifest_models.py +0 -0
  21. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/py.typed +0 -0
  22. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/services/api_key_service.py +0 -0
  23. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/services/catalog_service.py +0 -0
  24. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/services/download_catalog_service.py +0 -0
  25. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/services/download_request_service.py +0 -0
  26. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/services/download_rule_service.py +0 -0
  27. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/services/employee_service.py +0 -0
  28. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/services/employer_service.py +0 -0
  29. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/services/invoice_service.py +0 -0
  30. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/services/manifest_service.py +0 -0
  31. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/services/people_service.py +0 -0
  32. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/services/product_service.py +0 -0
  33. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi/services/tax_file_service.py +0 -0
  34. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi.egg-info/dependency_links.txt +0 -0
  35. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi.egg-info/requires.txt +0 -0
  36. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/fiscalapi.egg-info/top_level.txt +0 -0
  37. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/pyproject.toml +0 -0
  38. {fiscalapi-4.0.386 → fiscalapi-4.0.417}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fiscalapi
3
- Version: 4.0.386
3
+ Version: 4.0.417
4
4
  Summary: Genera facturas CFDI válidas ante el SAT consumiendo el API de https://www.fiscalapi.com
5
5
  Home-page: https://www.fiscalapi.com
6
6
  Author: Fiscalapi
@@ -76,10 +76,7 @@ Dynamic: summary
76
76
  - **Configuración de datos fiscales** (RFC, domicilio fiscal, régimen fiscal)
77
77
  - **Datos de empleado** (agrega/actualiza/elimina datos de empleado a una persona. CFDI Nómina)
78
78
  - **Datos de empleador** (agrega/actualiza/elimina datos de empleador a una persona. CFDI Nómina)
79
-
80
- ## 🎖️ Gestión de Timbres
81
- - **Gestión de folios fiscales** Compra timbres a fiscalapi y transfiere/retira a las personas de tu organizacion segun tus reglas de negocio.
82
-
79
+ - **Firma de carta manifiesto** (firma el manifiesto de una persona con su e.firma/FIEL)
83
80
 
84
81
  ## 🛍️ Gestión de Productos/Servicios
85
82
  - **Gestión de productos y servicios** con catálogo personalizable
@@ -91,11 +88,21 @@ Dynamic: summary
91
88
  - **Búsqueda de información** en catálogos del SAT con filtros avanzados
92
89
  - **Acceso y búsqueda** en catálogos completos
93
90
 
94
- ## 🎫 Gestión de Timbres
95
- - **Listar transacciones de timbres** con paginación
91
+ ## 🎫 Gestión de Timbres y Créditos de Validación
92
+ - **Gestión de folios fiscales**: compra timbres a FiscalAPI y transfiere/retira a las personas de tu organización según tus reglas de negocio
93
+ - **Listar transacciones** del ledger con paginación
96
94
  - **Consultar transacciones** por ID
97
95
  - **Transferir timbres** entre personas
98
96
  - **Retirar timbres** de una persona
97
+ - **Transferir y retirar créditos de validación SAT** con `credit_type=CreditType.VALIDATION`
98
+
99
+ ## 🛡️ Validaciones SAT
100
+ - **Estructura del XML** contra el Anexo 20 y los complementos declarados
101
+ - **Vigencia del certificado** del emisor a la fecha de emisión
102
+ - **Sello del CFDI** y **sello del SAT** en el Timbre Fiscal Digital
103
+ - **Estatus del comprobante** en el SAT (vigente, cancelado, no encontrado)
104
+ - **Listas negras del artículo 69-B y 69-B Bis** del CFF, por CFDI o por RFC
105
+ - **Catálogo de tipos de validación** y de los estatus que cada uno puede tomar
99
106
 
100
107
  ## 📖 Recursos Adicionales
101
108
  - **Cientos de ejemplos de código** disponibles en múltiples lenguajes de programación
@@ -357,6 +364,211 @@ else:
357
364
  print(api_response.message)
358
365
  ```
359
366
 
367
+ ### 8. Validaciones SAT
368
+
369
+ Cada tipo de validación solicitado consume un crédito de validación. El cobro es todo o nada: si el saldo no alcanza para todos, no se ejecuta ninguno y la API responde 403.
370
+
371
+ ```python
372
+ import base64
373
+ from pathlib import Path
374
+ from fiscalapi import SatValidationRequest, SatValidationTypeIds
375
+
376
+ # Validar un CFDI timbrado: con xml puedes solicitar cualquier tipo de validación
377
+ request = SatValidationRequest(
378
+ xml=base64.b64encode(Path("factura.xml").read_bytes()).decode("ascii"),
379
+ validation_types=[
380
+ SatValidationTypeIds.XML_STRUCTURE,
381
+ SatValidationTypeIds.CFDI_SELLO,
382
+ SatValidationTypeIds.CFDI_STATUS,
383
+ SatValidationTypeIds.BLACKLIST_69B,
384
+ ]
385
+ )
386
+
387
+ api_response = client.sat_validations.validate(request)
388
+
389
+ if api_response.succeeded:
390
+ for result in api_response.data:
391
+ veredicto = "PASSED" if result.passed else "FAILED"
392
+ print(f"[{veredicto}] {result.type.id.value} -> {result.status.id.value}")
393
+ if result.status.details:
394
+ print(f" {result.status.details}")
395
+ else:
396
+ print(api_response.details)
397
+ ```
398
+
399
+ Para consultar únicamente listas negras basta el RFC, sin enviar el CFDI:
400
+
401
+ ```python
402
+ request = SatValidationRequest(
403
+ tin="FUNK671228PH6",
404
+ validation_types=[
405
+ SatValidationTypeIds.BLACKLIST_69B,
406
+ SatValidationTypeIds.BLACKLIST_69B_BIS,
407
+ ]
408
+ )
409
+
410
+ api_response = client.sat_validations.validate(request)
411
+ ```
412
+
413
+ Envía `xml` o `tin`, nunca ambos y nunca ninguno: con `tin` solo se pueden solicitar listas negras.
414
+
415
+ El catálogo de tipos y los estatus que cada tipo puede tomar se consultan sin consumir créditos:
416
+
417
+ ```python
418
+ api_response = client.sat_validations.get_types()
419
+ api_response = client.sat_validations.get_type_by_id(SatValidationTypeIds.CFDI_STATUS)
420
+ api_response = client.sat_validations.get_statuses(SatValidationTypeIds.CFDI_STATUS)
421
+ ```
422
+
423
+ ### 9. Transferir Créditos de Validación
424
+
425
+ Los saldos nunca se mezclan: `CreditType.STAMP` mueve timbres (`available_balance`) y `CreditType.VALIDATION` mueve créditos de validación (`available_validation_balance`).
426
+
427
+ ```python
428
+ from fiscalapi import CreditType, StampTransactionParams
429
+
430
+ params = StampTransactionParams(
431
+ from_person_id="3f3478b4-60fd-459e-8bfc-f8239fc96257",
432
+ to_person_id="96b46762-d246-4a67-a562-510a25dbafa9",
433
+ amount=10,
434
+ comments="Asignación de créditos de validación",
435
+ credit_type=CreditType.VALIDATION
436
+ )
437
+
438
+ api_response = client.stamps.transfer_stamps(params)
439
+ ```
440
+
441
+ ### 10. Factura de Comercio Exterior
442
+
443
+ El complemento se envía en `Invoice.complement.comercio_exterior`. Puede combinarse con carta porte en el mismo comprobante.
444
+
445
+ > **Escala decimal:** el SAT valida la cantidad de decimales de varios campos. Construye siempre los montos con `Decimal("...")` a partir de una cadena con la escala exacta (`Decimal("0.160000")`, no `Decimal(0.16)`): el SDK preserva los ceros finales al serializar. Una escala incorrecta produce rechazos `CFDI40179` (en `taxRate`) o `CCE122` (en `valorDolares`).
446
+
447
+ > **Tipo de cambio:** `tipo_cambio_usd` debe ser el publicado en el DOF para la fecha de emisión, y la fecha del comprobante no puede exceder 72 horas al momento del timbrado. Si no coinciden, el PAC responde `CCE121` e indica el valor esperado.
448
+
449
+ ```python
450
+ from datetime import datetime
451
+ from decimal import Decimal
452
+
453
+ from fiscalapi.models import (
454
+ Invoice, InvoiceIssuer, InvoiceRecipient, InvoiceItem, ItemTax,
455
+ InvoiceComplement, TaxCredential,
456
+ ComercioExteriorComplement, ComercioExteriorEmisor, ComercioExteriorEmisorDomicilio,
457
+ ComercioExteriorReceptor, ComercioExteriorReceptorDomicilio, ComercioExteriorMercancia,
458
+ )
459
+
460
+ invoice = Invoice(
461
+ version_code="4.0",
462
+ payment_form_code="99",
463
+ payment_method_code="PPD",
464
+ currency_code="USD",
465
+ type_code="I",
466
+ expedition_zip_code="42501",
467
+ series="CCE",
468
+ date=datetime.now().replace(microsecond=0),
469
+ export_code="02",
470
+ issuer=InvoiceIssuer(
471
+ tin="EKU9003173C9",
472
+ legal_name="ESCUELA KEMPER URGATE",
473
+ tax_regime_code="601",
474
+ tax_credentials=[
475
+ TaxCredential(base64_file="<CER_BASE64>", file_type=0, password="<CSD_PASSWORD>"),
476
+ TaxCredential(base64_file="<KEY_BASE64>", file_type=1, password="<CSD_PASSWORD>"),
477
+ ],
478
+ ),
479
+ # Receptor extranjero: country_id (ResidenciaFiscal) es obligatorio si se envía foreign_tin.
480
+ recipient=InvoiceRecipient(
481
+ tin="XEXX010101000",
482
+ legal_name="U.S. 0026 SW",
483
+ zip_code="42501",
484
+ tax_regime_code="616",
485
+ cfdi_use_code="S01",
486
+ country_id="USA",
487
+ foreign_tin="123456789",
488
+ ),
489
+ items=[
490
+ InvoiceItem(
491
+ item_code="50211503",
492
+ item_sku="131494-1055", # debe coincidir con mercancia.no_identificacion
493
+ quantity=Decimal("2"),
494
+ unit_of_measurement_code="H87",
495
+ description="Cigarros",
496
+ unit_price=Decimal("200.00"),
497
+ discount=Decimal("0"),
498
+ tax_object_code="02",
499
+ item_taxes=[
500
+ ItemTax(tax_code="002", tax_type_code="Tasa", tax_rate=Decimal("0.160000"), tax_flag_code="T"),
501
+ ],
502
+ ),
503
+ ],
504
+ complement=InvoiceComplement(
505
+ comercio_exterior=ComercioExteriorComplement(
506
+ clave_de_pedimento_id="A1",
507
+ certificado_origen=0, # 0 = no funge como certificado de origen, 1 = sí
508
+ incoterm_id="FOB",
509
+ tipo_cambio_usd=Decimal("16.9722"),
510
+ # Domicilio del emisor: campos de catálogo SAT, con sufijo Id.
511
+ emisor=ComercioExteriorEmisor(
512
+ domicilio=ComercioExteriorEmisorDomicilio(
513
+ calle="CALLE DEL PAPEL",
514
+ colonia_id="0214",
515
+ localidad_id="01",
516
+ municipio_id="014",
517
+ estado_id="QUE",
518
+ pais_id="MEX",
519
+ codigo_postal_id="76199",
520
+ ),
521
+ ),
522
+ # Domicilio del receptor: texto libre, sin sufijo Id (salvo pais_id).
523
+ receptor=ComercioExteriorReceptor(
524
+ num_reg_id_trib="123456789",
525
+ domicilio=ComercioExteriorReceptorDomicilio(
526
+ calle="ST. A",
527
+ estado="TX",
528
+ pais_id="USA",
529
+ codigo_postal="00000",
530
+ ),
531
+ ),
532
+ mercancias=[
533
+ ComercioExteriorMercancia(
534
+ no_identificacion="131494-1055",
535
+ fraccion_arancelaria_id="2402200100",
536
+ cantidad_aduana=Decimal("117.64"),
537
+ unidad_aduana_id="01",
538
+ valor_unitario_aduana=Decimal("3.40"),
539
+ valor_dolares=Decimal("400.00"),
540
+ ),
541
+ ],
542
+ ),
543
+ ),
544
+ )
545
+
546
+ api_response = client.invoices.create(invoice)
547
+ ```
548
+
549
+ ### 11. Firmar Carta Manifiesto
550
+
551
+ Firma la carta manifiesto de una persona y devuelve el PDF resultante en base64.
552
+
553
+ Requiere la **e.firma (FIEL)** del contribuyente, no el CSD de timbrado, y que exista una persona con ese RFC en el tenant.
554
+
555
+ ```python
556
+ from fiscalapi.models import SignManifestRequest
557
+
558
+ api_response = client.manifests.sign(SignManifestRequest(
559
+ base64_cer="<FIEL_CER_BASE64>",
560
+ base64_key="<FIEL_KEY_BASE64>",
561
+ password="<FIEL_PASSWORD>",
562
+ ))
563
+
564
+ if api_response.succeeded:
565
+ print(api_response.data.file_name) # EKU9003173C9.pdf
566
+ print(api_response.data.file_extension) # .pdf
567
+ print(api_response.data.base64_file) # PDF firmado en base64
568
+ ```
569
+
570
+ Tras una firma exitosa, `client.people.get_by_id(...)` refleja el nuevo estado en `manifest_status_id`.
571
+
360
572
  ## 📋 Operaciones Principales
361
573
 
362
574
  - **Facturas (CFDI)**
@@ -365,16 +577,26 @@ else:
365
577
  Alta y administración de personas, gestión de certificados (CSD).
366
578
  - **Productos y Servicios**
367
579
  Administración de catálogos de productos, búsqueda en catálogos SAT.
368
- - **Timbres**
369
- Listar transacciones, transferir y retirar timbres entre personas.
580
+ - **Timbres y créditos de validación**
581
+ Listar transacciones, transferir y retirar timbres o créditos de validación entre personas.
582
+ - **Validaciones SAT**
583
+ Validar estructura, certificado, sellos, estatus en el SAT y listas negras 69-B de un CFDI o un RFC.
584
+ - **Carta manifiesto**
585
+ Firmar la carta manifiesto de una persona con su e.firma (FIEL) y obtener el PDF resultante.
370
586
 
371
587
  ## 📂 Más Ejemplos
372
588
 
373
589
  - [Gestión de Timbres](examples/ejemplos-timbres.py)
590
+ - [Validaciones SAT](examples/ejemplos-validaciones-sat.py)
374
591
  - [Complementos de Pago](examples/ejemplos-facturas-de-complemento-pago.py)
375
592
  - [Facturas de Nómina](examples/ejemplos-facturas-de-nomina.py)
376
593
  - [Impuestos Locales (Por Valores)](examples/ejemplos-factura-impuestos-locales-valores.py)
377
594
  - [Impuestos Locales (Por Referencias)](examples/ejemplos-factura-impuestos-locales-referencias.py)
595
+ - [Comercio Exterior (Por Valores)](examples/ejemplos-factura-comercio-exterior-valores.py)
596
+ - [Comercio Exterior (Por Referencias)](examples/ejemplos-factura-comercio-exterior-referencias.py)
597
+ - [Carta Porte (Por Valores)](examples/ejemplos-factura-carta-porte-valores.py)
598
+ - [Carta Porte (Por Referencias)](examples/ejemplos-factura-carta-porte-referencias.py)
599
+ - [Firma de Cartas Manifiesto](examples/ejemplos-firma-manifiestos.py)
378
600
 
379
601
  ## 🤝 Contribuir
380
602
 
@@ -38,10 +38,7 @@
38
38
  - **Configuración de datos fiscales** (RFC, domicilio fiscal, régimen fiscal)
39
39
  - **Datos de empleado** (agrega/actualiza/elimina datos de empleado a una persona. CFDI Nómina)
40
40
  - **Datos de empleador** (agrega/actualiza/elimina datos de empleador a una persona. CFDI Nómina)
41
-
42
- ## 🎖️ Gestión de Timbres
43
- - **Gestión de folios fiscales** Compra timbres a fiscalapi y transfiere/retira a las personas de tu organizacion segun tus reglas de negocio.
44
-
41
+ - **Firma de carta manifiesto** (firma el manifiesto de una persona con su e.firma/FIEL)
45
42
 
46
43
  ## 🛍️ Gestión de Productos/Servicios
47
44
  - **Gestión de productos y servicios** con catálogo personalizable
@@ -53,11 +50,21 @@
53
50
  - **Búsqueda de información** en catálogos del SAT con filtros avanzados
54
51
  - **Acceso y búsqueda** en catálogos completos
55
52
 
56
- ## 🎫 Gestión de Timbres
57
- - **Listar transacciones de timbres** con paginación
53
+ ## 🎫 Gestión de Timbres y Créditos de Validación
54
+ - **Gestión de folios fiscales**: compra timbres a FiscalAPI y transfiere/retira a las personas de tu organización según tus reglas de negocio
55
+ - **Listar transacciones** del ledger con paginación
58
56
  - **Consultar transacciones** por ID
59
57
  - **Transferir timbres** entre personas
60
58
  - **Retirar timbres** de una persona
59
+ - **Transferir y retirar créditos de validación SAT** con `credit_type=CreditType.VALIDATION`
60
+
61
+ ## 🛡️ Validaciones SAT
62
+ - **Estructura del XML** contra el Anexo 20 y los complementos declarados
63
+ - **Vigencia del certificado** del emisor a la fecha de emisión
64
+ - **Sello del CFDI** y **sello del SAT** en el Timbre Fiscal Digital
65
+ - **Estatus del comprobante** en el SAT (vigente, cancelado, no encontrado)
66
+ - **Listas negras del artículo 69-B y 69-B Bis** del CFF, por CFDI o por RFC
67
+ - **Catálogo de tipos de validación** y de los estatus que cada uno puede tomar
61
68
 
62
69
  ## 📖 Recursos Adicionales
63
70
  - **Cientos de ejemplos de código** disponibles en múltiples lenguajes de programación
@@ -319,6 +326,211 @@ else:
319
326
  print(api_response.message)
320
327
  ```
321
328
 
329
+ ### 8. Validaciones SAT
330
+
331
+ Cada tipo de validación solicitado consume un crédito de validación. El cobro es todo o nada: si el saldo no alcanza para todos, no se ejecuta ninguno y la API responde 403.
332
+
333
+ ```python
334
+ import base64
335
+ from pathlib import Path
336
+ from fiscalapi import SatValidationRequest, SatValidationTypeIds
337
+
338
+ # Validar un CFDI timbrado: con xml puedes solicitar cualquier tipo de validación
339
+ request = SatValidationRequest(
340
+ xml=base64.b64encode(Path("factura.xml").read_bytes()).decode("ascii"),
341
+ validation_types=[
342
+ SatValidationTypeIds.XML_STRUCTURE,
343
+ SatValidationTypeIds.CFDI_SELLO,
344
+ SatValidationTypeIds.CFDI_STATUS,
345
+ SatValidationTypeIds.BLACKLIST_69B,
346
+ ]
347
+ )
348
+
349
+ api_response = client.sat_validations.validate(request)
350
+
351
+ if api_response.succeeded:
352
+ for result in api_response.data:
353
+ veredicto = "PASSED" if result.passed else "FAILED"
354
+ print(f"[{veredicto}] {result.type.id.value} -> {result.status.id.value}")
355
+ if result.status.details:
356
+ print(f" {result.status.details}")
357
+ else:
358
+ print(api_response.details)
359
+ ```
360
+
361
+ Para consultar únicamente listas negras basta el RFC, sin enviar el CFDI:
362
+
363
+ ```python
364
+ request = SatValidationRequest(
365
+ tin="FUNK671228PH6",
366
+ validation_types=[
367
+ SatValidationTypeIds.BLACKLIST_69B,
368
+ SatValidationTypeIds.BLACKLIST_69B_BIS,
369
+ ]
370
+ )
371
+
372
+ api_response = client.sat_validations.validate(request)
373
+ ```
374
+
375
+ Envía `xml` o `tin`, nunca ambos y nunca ninguno: con `tin` solo se pueden solicitar listas negras.
376
+
377
+ El catálogo de tipos y los estatus que cada tipo puede tomar se consultan sin consumir créditos:
378
+
379
+ ```python
380
+ api_response = client.sat_validations.get_types()
381
+ api_response = client.sat_validations.get_type_by_id(SatValidationTypeIds.CFDI_STATUS)
382
+ api_response = client.sat_validations.get_statuses(SatValidationTypeIds.CFDI_STATUS)
383
+ ```
384
+
385
+ ### 9. Transferir Créditos de Validación
386
+
387
+ Los saldos nunca se mezclan: `CreditType.STAMP` mueve timbres (`available_balance`) y `CreditType.VALIDATION` mueve créditos de validación (`available_validation_balance`).
388
+
389
+ ```python
390
+ from fiscalapi import CreditType, StampTransactionParams
391
+
392
+ params = StampTransactionParams(
393
+ from_person_id="3f3478b4-60fd-459e-8bfc-f8239fc96257",
394
+ to_person_id="96b46762-d246-4a67-a562-510a25dbafa9",
395
+ amount=10,
396
+ comments="Asignación de créditos de validación",
397
+ credit_type=CreditType.VALIDATION
398
+ )
399
+
400
+ api_response = client.stamps.transfer_stamps(params)
401
+ ```
402
+
403
+ ### 10. Factura de Comercio Exterior
404
+
405
+ El complemento se envía en `Invoice.complement.comercio_exterior`. Puede combinarse con carta porte en el mismo comprobante.
406
+
407
+ > **Escala decimal:** el SAT valida la cantidad de decimales de varios campos. Construye siempre los montos con `Decimal("...")` a partir de una cadena con la escala exacta (`Decimal("0.160000")`, no `Decimal(0.16)`): el SDK preserva los ceros finales al serializar. Una escala incorrecta produce rechazos `CFDI40179` (en `taxRate`) o `CCE122` (en `valorDolares`).
408
+
409
+ > **Tipo de cambio:** `tipo_cambio_usd` debe ser el publicado en el DOF para la fecha de emisión, y la fecha del comprobante no puede exceder 72 horas al momento del timbrado. Si no coinciden, el PAC responde `CCE121` e indica el valor esperado.
410
+
411
+ ```python
412
+ from datetime import datetime
413
+ from decimal import Decimal
414
+
415
+ from fiscalapi.models import (
416
+ Invoice, InvoiceIssuer, InvoiceRecipient, InvoiceItem, ItemTax,
417
+ InvoiceComplement, TaxCredential,
418
+ ComercioExteriorComplement, ComercioExteriorEmisor, ComercioExteriorEmisorDomicilio,
419
+ ComercioExteriorReceptor, ComercioExteriorReceptorDomicilio, ComercioExteriorMercancia,
420
+ )
421
+
422
+ invoice = Invoice(
423
+ version_code="4.0",
424
+ payment_form_code="99",
425
+ payment_method_code="PPD",
426
+ currency_code="USD",
427
+ type_code="I",
428
+ expedition_zip_code="42501",
429
+ series="CCE",
430
+ date=datetime.now().replace(microsecond=0),
431
+ export_code="02",
432
+ issuer=InvoiceIssuer(
433
+ tin="EKU9003173C9",
434
+ legal_name="ESCUELA KEMPER URGATE",
435
+ tax_regime_code="601",
436
+ tax_credentials=[
437
+ TaxCredential(base64_file="<CER_BASE64>", file_type=0, password="<CSD_PASSWORD>"),
438
+ TaxCredential(base64_file="<KEY_BASE64>", file_type=1, password="<CSD_PASSWORD>"),
439
+ ],
440
+ ),
441
+ # Receptor extranjero: country_id (ResidenciaFiscal) es obligatorio si se envía foreign_tin.
442
+ recipient=InvoiceRecipient(
443
+ tin="XEXX010101000",
444
+ legal_name="U.S. 0026 SW",
445
+ zip_code="42501",
446
+ tax_regime_code="616",
447
+ cfdi_use_code="S01",
448
+ country_id="USA",
449
+ foreign_tin="123456789",
450
+ ),
451
+ items=[
452
+ InvoiceItem(
453
+ item_code="50211503",
454
+ item_sku="131494-1055", # debe coincidir con mercancia.no_identificacion
455
+ quantity=Decimal("2"),
456
+ unit_of_measurement_code="H87",
457
+ description="Cigarros",
458
+ unit_price=Decimal("200.00"),
459
+ discount=Decimal("0"),
460
+ tax_object_code="02",
461
+ item_taxes=[
462
+ ItemTax(tax_code="002", tax_type_code="Tasa", tax_rate=Decimal("0.160000"), tax_flag_code="T"),
463
+ ],
464
+ ),
465
+ ],
466
+ complement=InvoiceComplement(
467
+ comercio_exterior=ComercioExteriorComplement(
468
+ clave_de_pedimento_id="A1",
469
+ certificado_origen=0, # 0 = no funge como certificado de origen, 1 = sí
470
+ incoterm_id="FOB",
471
+ tipo_cambio_usd=Decimal("16.9722"),
472
+ # Domicilio del emisor: campos de catálogo SAT, con sufijo Id.
473
+ emisor=ComercioExteriorEmisor(
474
+ domicilio=ComercioExteriorEmisorDomicilio(
475
+ calle="CALLE DEL PAPEL",
476
+ colonia_id="0214",
477
+ localidad_id="01",
478
+ municipio_id="014",
479
+ estado_id="QUE",
480
+ pais_id="MEX",
481
+ codigo_postal_id="76199",
482
+ ),
483
+ ),
484
+ # Domicilio del receptor: texto libre, sin sufijo Id (salvo pais_id).
485
+ receptor=ComercioExteriorReceptor(
486
+ num_reg_id_trib="123456789",
487
+ domicilio=ComercioExteriorReceptorDomicilio(
488
+ calle="ST. A",
489
+ estado="TX",
490
+ pais_id="USA",
491
+ codigo_postal="00000",
492
+ ),
493
+ ),
494
+ mercancias=[
495
+ ComercioExteriorMercancia(
496
+ no_identificacion="131494-1055",
497
+ fraccion_arancelaria_id="2402200100",
498
+ cantidad_aduana=Decimal("117.64"),
499
+ unidad_aduana_id="01",
500
+ valor_unitario_aduana=Decimal("3.40"),
501
+ valor_dolares=Decimal("400.00"),
502
+ ),
503
+ ],
504
+ ),
505
+ ),
506
+ )
507
+
508
+ api_response = client.invoices.create(invoice)
509
+ ```
510
+
511
+ ### 11. Firmar Carta Manifiesto
512
+
513
+ Firma la carta manifiesto de una persona y devuelve el PDF resultante en base64.
514
+
515
+ Requiere la **e.firma (FIEL)** del contribuyente, no el CSD de timbrado, y que exista una persona con ese RFC en el tenant.
516
+
517
+ ```python
518
+ from fiscalapi.models import SignManifestRequest
519
+
520
+ api_response = client.manifests.sign(SignManifestRequest(
521
+ base64_cer="<FIEL_CER_BASE64>",
522
+ base64_key="<FIEL_KEY_BASE64>",
523
+ password="<FIEL_PASSWORD>",
524
+ ))
525
+
526
+ if api_response.succeeded:
527
+ print(api_response.data.file_name) # EKU9003173C9.pdf
528
+ print(api_response.data.file_extension) # .pdf
529
+ print(api_response.data.base64_file) # PDF firmado en base64
530
+ ```
531
+
532
+ Tras una firma exitosa, `client.people.get_by_id(...)` refleja el nuevo estado en `manifest_status_id`.
533
+
322
534
  ## 📋 Operaciones Principales
323
535
 
324
536
  - **Facturas (CFDI)**
@@ -327,16 +539,26 @@ else:
327
539
  Alta y administración de personas, gestión de certificados (CSD).
328
540
  - **Productos y Servicios**
329
541
  Administración de catálogos de productos, búsqueda en catálogos SAT.
330
- - **Timbres**
331
- Listar transacciones, transferir y retirar timbres entre personas.
542
+ - **Timbres y créditos de validación**
543
+ Listar transacciones, transferir y retirar timbres o créditos de validación entre personas.
544
+ - **Validaciones SAT**
545
+ Validar estructura, certificado, sellos, estatus en el SAT y listas negras 69-B de un CFDI o un RFC.
546
+ - **Carta manifiesto**
547
+ Firmar la carta manifiesto de una persona con su e.firma (FIEL) y obtener el PDF resultante.
332
548
 
333
549
  ## 📂 Más Ejemplos
334
550
 
335
551
  - [Gestión de Timbres](examples/ejemplos-timbres.py)
552
+ - [Validaciones SAT](examples/ejemplos-validaciones-sat.py)
336
553
  - [Complementos de Pago](examples/ejemplos-facturas-de-complemento-pago.py)
337
554
  - [Facturas de Nómina](examples/ejemplos-facturas-de-nomina.py)
338
555
  - [Impuestos Locales (Por Valores)](examples/ejemplos-factura-impuestos-locales-valores.py)
339
556
  - [Impuestos Locales (Por Referencias)](examples/ejemplos-factura-impuestos-locales-referencias.py)
557
+ - [Comercio Exterior (Por Valores)](examples/ejemplos-factura-comercio-exterior-valores.py)
558
+ - [Comercio Exterior (Por Referencias)](examples/ejemplos-factura-comercio-exterior-referencias.py)
559
+ - [Carta Porte (Por Valores)](examples/ejemplos-factura-carta-porte-valores.py)
560
+ - [Carta Porte (Por Referencias)](examples/ejemplos-factura-carta-porte-referencias.py)
561
+ - [Firma de Cartas Manifiesto](examples/ejemplos-firma-manifiestos.py)
340
562
 
341
563
  ## 🤝 Contribuir
342
564
 
@@ -66,6 +66,15 @@ from .models.manifest_models import (
66
66
  SignManifestRequest,
67
67
  SignManifestResponse,
68
68
  )
69
+ from .models.sat_validation_models import (
70
+ SatValidationTypeIds,
71
+ SatValidationStatusIds,
72
+ SatValidationRequest,
73
+ SatValidationType,
74
+ SatValidationTypeStatus,
75
+ SatValidationStatus,
76
+ SatValidationResult,
77
+ )
69
78
 
70
79
  # Modelos de dominio
71
80
  from .models.fiscalapi_models import (
@@ -111,7 +120,6 @@ from .models.fiscalapi_models import (
111
120
  PayrollDeduction,
112
121
  PayrollDisability,
113
122
  PayrollComplement,
114
- CartaPorteComplement,
115
123
  InvoiceComplement,
116
124
  # Invoice models
117
125
  InvoiceResponse,
@@ -142,6 +150,9 @@ from .models.fiscalapi_models import (
142
150
  XmlComplement,
143
151
  Xml,
144
152
  # Stamp models
153
+ CreditType,
154
+ StampTransactionType,
155
+ StampTransactionStatus,
145
156
  UserLookupDto,
146
157
  StampTransaction,
147
158
  StampTransactionParams,
@@ -160,6 +171,7 @@ from .services.invoice_service import InvoiceService
160
171
  from .services.people_service import PeopleService
161
172
  from .services.product_service import ProductService
162
173
  from .services.tax_file_service import TaxFileService
174
+ from .services.sat_validation_service import SatValidationService
163
175
  from .services.stamp_service import StampService
164
176
  from .services.manifest_service import ManifestService
165
177
 
@@ -216,7 +228,6 @@ __all__ = [
216
228
  "PayrollDeduction",
217
229
  "PayrollDisability",
218
230
  "PayrollComplement",
219
- "CartaPorteComplement",
220
231
  "InvoiceComplement",
221
232
  # Carta Porte models
222
233
  "UbicacionDomicilio",
@@ -224,6 +235,7 @@ __all__ = [
224
235
  "RegimenAduanero",
225
236
  "Ubicacion",
226
237
  "DocumentoAduanero",
238
+ "GuiaIdentificacion",
227
239
  "CantidadTransporta",
228
240
  "DetalleMercancia",
229
241
  "Mercancia",
@@ -280,12 +292,23 @@ __all__ = [
280
292
  "XmlComplement",
281
293
  "Xml",
282
294
  # Stamp models
295
+ "CreditType",
296
+ "StampTransactionType",
297
+ "StampTransactionStatus",
283
298
  "UserLookupDto",
284
299
  "StampTransaction",
285
300
  "StampTransactionParams",
286
301
  # Manifest models
287
302
  "SignManifestRequest",
288
303
  "SignManifestResponse",
304
+ # SAT validation models
305
+ "SatValidationTypeIds",
306
+ "SatValidationStatusIds",
307
+ "SatValidationRequest",
308
+ "SatValidationType",
309
+ "SatValidationTypeStatus",
310
+ "SatValidationStatus",
311
+ "SatValidationResult",
289
312
  # Servicios
290
313
  "BaseService",
291
314
  "ApiKeyService",
@@ -299,6 +322,7 @@ __all__ = [
299
322
  "PeopleService",
300
323
  "ProductService",
301
324
  "TaxFileService",
325
+ "SatValidationService",
302
326
  "StampService",
303
327
  "ManifestService",
304
328
  # Cliente principal