Skip to content

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 ContextEntidad raízEstado de migración
empresaEmpresa (RFC, RazonSocial, ProdigiaRFC, ServicesRFC)DDD completo
solicitudSolicitud (UUID, RFC, período, TiposFactura, Estatus)DDD completo
conciliacionConciliacion (RFCSolicitante, FacturasConciliadas[])DDD completo
reporteReporte (RFC, SolicitudUUID, R21, RAMCI, Estatus)DDD completo
facturaFacturaERP / FacturaProdigia (value objects)Use cases
authSesionUsuario, JWTDDD completo
notificacionNotificacion (RFC, Titulo, Mensaje, Leido)DDD completo
cfdi_cacheCFDICacheEntry (PostgreSQL + Azure Blob)Parcial
erp_cacheERPCacheEntryParcial
solicitud_assetSolicitudAssetDDD completo
comprobanteComprobanteDownloadParcial
certificadoCertificateInfo, TokenResponseParcial
conceptos_pasivosSin domain layer
sharedErrores, Logger, MathUtils, RFCPermanente

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 Hades
empresa.GetServicesRFC() // consultas al ERP e Importa
empresa.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 ← InvalidTransitionError

Contexto 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) float64
SumarIVARetenido(impuestos []Impuesto) float64
SumarISRRetenido(impuestos []Impuesto) float64
TasaIVAPredominante(impuestos []Impuesto) float64

Contexto 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, funciones IsNotFound(), 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ónomo

Resumen

  • El sistema se organiza en 14 bounded contexts, cada uno con entidad raíz, repositorio e interfaces propias.
  • El contexto empresa implementa RFC multi-tenant con tres RFC distintos: base (BD), ProdigiaRFC (SAT) y ServicesRFC (ERP).
  • El ciclo de vida de Solicitud tiene un único estado final e inmutable: Conciliada. El intento de salir de él produce InvalidTransitionError.
  • Los arrays ImpuestosERP e ImpuestosProdigia nunca se mezclan; esta invariante es verificable en tests sin infraestructura.
  • El contexto shared es permanente y contiene la lógica matemática de redondeo half-up exigida por el SAT.