T2026-08-047 feat(fin): catálogos SAT, conceptos de facturación y datos fiscales del emisor #5
Reference in New Issue
Block a user
No description provided.
Delete Branch "feature/AS-catalogos-sat-facturacion"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
feat(fin): catálogos SAT, conceptos y datos fiscales del emisor
Rama:
feature/AS-catalogos-sat-facturacion→ base:feature/crm-cumplimiento-pdfTicket: T2026-08-047
Tipo de actividad IA: DISEÑO DE PROMPTS IA
Qué se implementó
Schema
sat— 9 catálogos globales de solo lecturaTablas sin
tenant_idnicompany_id, sin CRUD y sin baja física (las claves que elSAT retira se desactivan con
is_activepara no romper los CFDI históricos que lasreferencian):
sat.tax_regimesc_RegimenFiscalsat.taxesc_Impuestosat.payment_formsc_FormaPagosat.units_of_measurec_ClaveUnidadsat.products_servicesc_ClaveProdServsat.voucher_typesc_TipoDeComprobantesat.payment_methodsc_MetodoPagosat.tax_objectsc_ObjetoImpsat.cfdi_usesc_UsoCFDILas semillas viven en
backend/api/v1/modules/fin/catalogs/seed_data.py, no dentro dela migración: corregir un dato del catálogo no debe exigir escribir una migración de
esquema nueva.
sync_catalogs(connection)hace upsert por clave (inserta lo que falta,actualiza descripción y banderas, nunca borra) y queda disponible para futuras
actualizaciones del catálogo.
fin.concepts— conceptos de facturación por empresa (CRUD)Relación 1:1 con
sat.products_servicespor empresa, garantizada por índice únicoparcial (
WHERE deleted_at IS NULL) y validada en el service para devolver409con mensaje en español en vez de un
IntegrityErrorcrudo. La baja lógica libera laclave ProdServ y el código para un concepto nuevo. Las respuestas traen los objetos del
catálogo ya resueltos (
selectin) para evitar N+1 en la UI.fin.issuer_settings— datos fiscales del emisorRazón social, RFC, régimen fiscal y CP del lugar de expedición. Una sola configuración
vigente por empresa (índice único parcial);
GET+PUT(upsert), sinDELETE. ElRFC se valida con
^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$y se normaliza a mayúsculas sinespacios antes de aplicar el
max_length, para que un RFC con espacios de sobra nose rechace por longitud antes de limpiarlo.
Amarre a facturas
fin.invoices+voucher_type_id,payment_form_id,payment_method_id,expedition_zip_code(todas nullable).fin.invoice_items+concept_id,product_service_id,unit_of_measure_id,tax_object_id(todas nullable).fin.invoice_item_taxespara el detalle de impuestos trasladados y retenidospor partida. No interviene en el cálculo de subtotal/IVA/total, que sigue saliendo
de
invoices.tax_rate.invoice_items.concept(texto libre) se conserva intacta y obligatoria porque laconsume el PDF actual. Al crear una partida con
concept_idsinconcept, el servicehereda ahí la descripción del concepto (recortada a 60 caracteres).
Migraciones
e6f7a8b9c0d1(revisesd5e6f7a8b9c0): crea el schemasaty sus 8 tablasiniciales con sus índices, siembra los catálogos llamando a
sync_catalogs(op.get_bind()), creafin.concepts,fin.issuer_settingsyfin.invoice_item_taxes, y agrega las columnas y FKs nuevas. Eldowngrade()revierteen orden inverso y termina con
DROP SCHEMA sat CASCADE.f7a8b9c0d1e2(revisese6f7a8b9c0d1): agregasat.cfdi_uses, las FK fiscales decrm.accountsy el backfill del texto libre.sync_catalogsomite los cat��logos cuya tabla todavía no existe: al correr el historialdesde cero, la primera migración la invoca antes de que exista
sat.cfdi_uses, y cadacatálogo se siembra en la migración que lo crea.
Permisos
fin.concept.{view,create,edit,delete}(vía_ENTITIES).fin.settings.viewyfin.settings.edit, registrados a mano.fin.access.Tras el merge hay que sincronizar permisos:
python -m core.permissions.sync_cli(o el comando equivalente del proyecto,
backend/api/v1/modules/core/permissions/sync_cli.py)para que los permisos nuevos queden asignables desde Roles y permisos.
Frontend
src/lib/api/fin/{catalogs,concepts,issuer}.ts, reexportados desdeindex.ts. Loscatálogos se cachean en un
Mapdel módulo tras la primera carga./dashboard/fin/conceptos— tabla, buscador y filtro activo/inactivo. El alta y laedición viven en páginas dedicadas (
/nuevoy/[id]), siguiendo el patrón que ya usael CRM para proveedores y cuentas; los campos se comparten en
$lib/components/fin/ConceptFields.svelte, con combobox de clave ProdServ desde 2caracteres y el
409del backend mostrado junto al campo. Entrada en el sidebar conpermiso
fin.concept.view./dashboard/settings/facturacion— formulario del emisor con la misma validación deRFC que el backend. Si el
GETresponde404se abre en modo alta, no como error; elguardar se deshabilita sin
fin.settings.edit. Enlazada desde el índice deconfiguración y desde el sidebar con permiso
fin.settings.view./dashboard/fin/facturas/[id]— el selector de concepto de la partida se alimenta delcatálogo de conceptos de la empresa (manda
concept_id, precarga el precio unitario).Las claves genéricas que estaban fijas en el código quedan en un segundo grupo del
mismo selector, marcadas como «sin clave del SAT», para no bloquear a las empresas sin
catálogo. El listado etiqueta con la clave del catálogo cuando la partida la referencia
y cae al texto libre en las facturas anteriores.
Cómo probarlo
En la UI: Facturación → Conceptos (alta de un concepto, intentar repetir la clave
ProdServ → error junto al campo) y Configuración → Facturación (capturar RFC dummy
XAXX010101000, guardar, recargar).Salida de las pruebas
Migración contra PostgreSQL limpio (contenedor desechable, no la BD de desarrollo):
Conteo de semillas y índices parciales:
Idempotencia de
sync_catalogssobre PostgreSQL (2ª y 3ª corrida):Suite de pruebas del backend (70 previas + 36 nuevas):
Frontend:
Lint del backend (
flake8 --max-line-length=140sobre los archivos nuevos ymodificados): sin hallazgos. Los
E501que reportafin/invoices/routes.pyson previosa este cambio; ese archivo no se tocó.
Decisiones cerradas en este PR
Las 7 decisiones que originalmente quedaban abiertas se resolvieron así:
c_UsoCFDIsat.cfdi_uses(24 claves) +GET /fin/catalogs/cfdi-uses+ FKcrm.accounts.cfdi_use_id. Las claves quedan marcadas pendiente validación Fiscal, igual que el subset de ProdServ.c_ObjetoImp05–07sat.taxes. El ISH y similares viajan en el complemento Impuestos Locales con claves ajenas ac_Impuesto; mezclarlas rompería la unicidad del catálogo federal.is_localse conserva como bandera para cuando exista el catálogo del complemento, que es otro ticket.crm.accounts.tax_regime_idcon backfill conservador; el texto libre se conserva.company_iden los catálogosfin.access. La alternativa (montarlos fuera del router defin) exigiría una dependencia de autorización propia para datos que igual solo consume el módulo de Facturación; no compensa. El cliente del frontend envíacompany_id.create_itemyupdate_itemheredanproduct_service_id,unit_of_measure_idytax_object_iddel concepto; lo que envía el cliente gana.Backfill del régimen y uso de CFDI
La migración solo resuelve coincidencias inequívocas: el texto libre igual a la clave
del catálogo (
601,G03) o igual a la descripción exacta, sin distinguir mayúsculasni espacios sobrantes. Lo que no case se queda en
NULLy la ficha del cliente muestrael texto capturado junto al selector, para que un humano elija la clave. No se deduce
el régimen de un receptor a partir de texto libre: una clave equivocada provoca CFDI
rechazados.
Verificado contra PostgreSQL:
PENDIENTE DECISIÓN — bloqueado por datos, no por diseño
Quedan dos puntos abiertos. En ambos la estructura ya está lista y solo falta cargar
filas; no las escribí de memoria a propósito: una clave del SAT equivocada en un CFDI
es un problema fiscal real, y el ticket es explícito en que no se deducen ni se inventan.
Se cargan con
sync_catalogsen cuanto Fiscal entregue las filas oficiales, sin migraciónde esquema.
c_ObjetoImpclaves 05–07. Sembradas 01–04. Faltan las tres claves queincorporaron versiones posteriores del catálogo, con su descripción textual exacta.
Lo que necesito: las 3 filas (clave + descripción) de la versión vigente.
c_ClaveProdServadicionales. El subset de 11 claves no cubre serviciosaccesorios frecuentes del giro: maniobras, demoras/demurrage, custodia y servicios
aduanales. Lo que necesito: la clave de 8 dígitos y su descripción para cada
servicio que se vaya a facturar.
Además, dos validaciones que no bloquean el merge pero sí el paso a producción:
c_UsoCFDIy las 11 dec_ClaveProdServya sembradasrequieren visto bueno de Fiscal antes de timbrar. Ambos bloques están marcados con ese
comentario en
seed_data.py.c_UsoCFDI(qué uso admite cada régimeny tipo de persona). Cambia entre versiones del catálogo y equivocarla provoca rechazos
al timbrar; hoy la UI ofrece todos los usos. Si Fiscal la entrega, se agrega como
columnas del catálogo igual que las banderas de
c_RegimenFiscal.Fuera de alcance (confirmado)
Generación del XML CFDI 4.0, sellado, timbrado con PAC, cancelaciones, complemento de
pago y Carta Porte. Tampoco se tocó el cálculo de totales de
fin/invoices/service.pyni se cargó el catálogo completo de
c_ClaveProdServ(~52,000 claves).🤖 Generated with Claude Code
fin.invoices gana tipo de comprobante, forma y método de pago y CP de expedición; fin.invoice_items gana concepto de catálogo y las claves ProdServ, unidad y objeto de impuesto. Todas nullable: las facturas ya emitidas no las tienen y siguen funcionando igual (listado, detalle, PDF, envío). La columna de texto libre invoice_items.concept se conserva obligatoria porque la consume el PDF actual; al capturar por catálogo, el service hereda ahí la descripción del concepto cuando el cliente no la envía. Nueva tabla fin.invoice_item_taxes para el detalle de impuestos trasladados y retenidos por partida. No interviene en el cálculo de subtotal/IVA/total, que sigue saliendo de invoices.tax_rate. Incluye la migración e6f7a8b9c0d1 (crea el schema sat, siembra los catálogos con sync_catalogs y monta las tablas e índices nuevos) y registra los permisos fin.concept.* y fin.settings.{view,edit}. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>feat(fin): catálogos SAT, conceptos de facturación y datos fiscales del emisorto T2026-08-047 feat(fin): catálogos SAT, conceptos de facturación y datos fiscales del emisorView command line instructions
Checkout
From your project repository, check out a new branch and test the changes.