Bounded Contexts y Dominio
GM Fiscal Backend organiza su dominio en bounded contexts, cada uno con una entidad raíz, su propio repositorio y errores de dominio. Este modelo siguiendo los principios de Domain-Driven Design garantiza que cada contexto sea autónomo, cohesivo y modificable sin afectar a los demás.
Mapa de bounded contexts
| Bounded Context | Entidad raíz | Estado de migración |
|---|---|---|
empresa | Empresa (RFC, RazonSocial, ProdigiaRFC, ServicesRFC) | DDD completo |
solicitud | Solicitud (UUID, RFC, período, TiposFactura, Estatus) | DDD completo |
conciliacion | Conciliacion (RFCSolicitante, FacturasConciliadas[]) | DDD completo |
reporte | Reporte (RFC, SolicitudUUID, R21, RAMCI, Estatus) | DDD completo |
factura | FacturaERP / FacturaProdigia (value objects) | Use cases |
auth | SesionUsuario, JWT | DDD completo |
notificacion | Notificacion (RFC, Titulo, Mensaje, Leido) | DDD completo |
cfdi_cache | CFDICacheEntry (PostgreSQL + Azure Blob) | Parcial |
erp_cache | ERPCacheEntry | Parcial |
solicitud_asset | SolicitudAsset | DDD completo |
comprobante | ComprobanteDownload | Parcial |
certificado | CertificateInfo, TokenResponse | Parcial |
conceptos_pasivos | — | Sin domain layer |
shared | Errores, Logger, MathUtils, RFC | Permanente |
Contexto empresa
La entidad Empresa es el punto de partida de cualquier operación fiscal. Almacena hasta tres RFC distintos para cubrir el caso de modo TST (entorno de prueba con datos SAT reales):
type Empresa struct { RFC string // RFC base — almacenado en BD y usado como identificador ProdigiaRFC *string // RFC para consultas a Prodigia y al SAT (puede diferir) ServicesRFC *string // RFC para consultas al ERP e Importa (puede diferir)}Los métodos de acceso son obligatorios para cualquier consulta externa. Nunca se usa empresa.RFC directamente en llamadas a sistemas externos:
empresa.GetProdigiaRFC() // consultas SAT vía Prodigia o Hadesempresa.GetServicesRFC() // consultas al ERP e Importaempresa.GetDatabaseRFC() // identificador en base de datos (equivale a empresa.RFC)El modo TST permite ejecutar pruebas con datos reales del SAT usando un RFC de prueba como identificador de BD y el RFC real del cliente para las consultas externas.
Contexto solicitud
La Solicitud es la unidad de trabajo del sistema. Agrupa el RFC de la empresa, el período fiscal, los tipos de facturas a conciliar y el estado actual del proceso. Su ciclo de vida es un grafo de estados con una sola transición irreversible.
Ciclo de vida
stateDiagram-v2
direction LR
[*] --> ListoParaConciliar : Nueva solicitud creada
ListoParaConciliar --> EnProceso : Inicio de conciliación
EnProceso --> Conciliada : Conciliación exitosa
EnProceso --> Error : Fallo técnico
Error --> ListoParaConciliar : Reintento
Conciliada --> [*] : Estado final e inmutable
note right of Conciliada
No puede volver atrás.
Los reportes R21 y RAMCI
se generan desde este estado.
end note
Las transiciones están validadas a nivel de dominio. Cualquier intento de mover una solicitud desde Conciliada a cualquier otro estado produce un InvalidTransitionError. Esta invariante protege la integridad de los reportes generados a partir de una conciliación completada.
Transiciones permitidas: "Listo para conciliar" → "Solicitud en proceso" "Solicitud en proceso" → "Conciliada" "Solicitud en proceso" → "Error" "Error" → "Listo para conciliar"
Transiciones prohibidas: "Conciliada" → cualquier estado ← InvalidTransitionErrorContexto conciliacion
La Conciliacion almacena el resultado del cruce entre facturas ERP y facturas SAT. Su estructura central es FacturasConciliadas, una lista de facturas con su categoría de conciliación y los arrays de impuestos de cada origen preservados por separado.
Las invariantes son verificables en tests sin infraestructura:
for _, fc := range result.FacturasConciliadas { for _, imp := range fc.ImpuestosERP { // Todos los impuestos del array ERP deben tener origen "ERP" assert.Equal(t, "ERP", imp.Origen) } for _, imp := range fc.ImpuestosProdigia { // Todos los impuestos del array Prodigia deben tener origen "PRODIGIA" assert.Equal(t, "PRODIGIA", imp.Origen) }}Contexto reporte
Un Reporte agrega el resultado de aplicar R21Processor y RAMCIProcessor sobre los datos de una conciliación. Tiene su propio ciclo de vida asincrónico: nace en estado Pendiente, es procesado por el ReporteWorker y alcanza estado Completado cuando el Excel está disponible en Azure Blob.
El contexto de reporte incluye la lógica matemática crítica para cumplimiento SAT en domain/reporte/r21_math.go, que implementa la interfaz R21MathUtils.
Contexto factura
Los tipos FacturaERP y FacturaProdigia son value objects: estructuras de datos sin identidad propia que representan la factura desde la perspectiva de cada fuente. La separación de tipos garantiza que el compilador detecte en tiempo de compilación cualquier intento de mezclar datos de orígenes distintos.
El paquete domain/factura/impuestos.go expone funciones puras para operar sobre los impuestos:
SumarIVATrasladado(impuestos []Impuesto) float64SumarIVARetenido(impuestos []Impuesto) float64SumarISRRetenido(impuestos []Impuesto) float64TasaIVAPredominante(impuestos []Impuesto) float64Contexto shared
El bounded context shared es permanente y no se migra. Contiene los elementos transversales que todos los contextos pueden importar:
- Errores compartidos:
ErrNotFound,ErrAlreadyExists,InvalidTransitionError, funcionesIsNotFound(),IsConflict(). - Logger: interfaz del sistema de logging hacia GM Logs.
- MathUtils: utilidades de redondeo SAT (half-up), comparación con tolerancia y acumulación de montos.
- RFC utils: utilidades para normalización y validación de RFC.
Contexto notificacion
Gestiona el canal de comunicación en tiempo real con el cliente. Cuando un reporte se completa, el ReporteWorker invoca el puerto WebSocketPort definido en dominio, que en infraestructura se implementa mediante gorilla/websocket. El cliente se suscribe al canal de su RFC y recibe la notificación en cuanto el Excel está disponible.
Contexto auth
Gestiona la sesión del usuario. Al hacer login, GM Servicios valida las credenciales y emite un par de tokens JWT: access token (24 horas) y refresh token (30 días). La SesionUsuario se persiste con IP, user-agent y fechas para auditoría. El middleware JWTAuthMiddleware protege todas las rutas bajo /api/* excepto las de autenticación.
Si la empresa no existe en el sistema al primer login, EmpresaAutoRegistrationAdapter la registra automáticamente a partir de los datos del token JWT.
Estructura de directorios del dominio
src/domain/├── shared/ errors.go · logger.go · request_log_repo.go├── empresa/ entity · repository · errors · mocks/├── solicitud/ entity · repository · xml_repository · service · events · errors · mocks/├── conciliacion/ entity · repository · service · tax_utils · errors · mocks/├── reporte/ entity · repository · r21_math · r21_processor · r21_validator · ramci_processor · errors · mocks/├── factura/ value_objects · impuestos · provider_port · converters├── comprobante/ entity · repository├── notificacion/ entity · repository · websocket_port├── certificado/ service · value_objects├── auth/ repository (sesion)├── cfdi_cache/ entity · repository · errors · mocks/├── erp_cache/ entity · repository · errors · mocks/├── solicitud_asset/ entity · repository · errors · mocks/└── pre_conciliacion/ orquestación del pipeline autónomoResumen
- El sistema se organiza en 14 bounded contexts, cada uno con entidad raíz, repositorio e interfaces propias.
- El contexto
empresaimplementa RFC multi-tenant con tres RFC distintos: base (BD),ProdigiaRFC(SAT) yServicesRFC(ERP). - El ciclo de vida de
Solicitudtiene un único estado final e inmutable:Conciliada. El intento de salir de él produceInvalidTransitionError. - Los arrays
ImpuestosERPeImpuestosProdigianunca se mezclan; esta invariante es verificable en tests sin infraestructura. - El contexto
sharedes permanente y contiene la lógica matemática de redondeo half-up exigida por el SAT.