Arquitectura de Capas - Hexagonal Architecture¶
Descripción¶
Diagrama de capas siguiendo Clean Architecture / Hexagonal Architecture (Ports & Adapters). Muestra la separación entre capas de dominio, aplicación, infraestructura e interfaces.
Las dependencias fluyen hacia adentro: Infrastructure → Application → Domain.
Diagrama¶
graph TB
subgraph InterfacesLayer["Interfaces Layer (UI + API)"]
GUI[Streamlit GUI<br/>app.py]
CLI[CLI Interface<br/>cli.py<br/>✅ 20 comandos]
API[FastAPI REST<br/>api/main.py<br/>✅ 22 endpoints<br/>JWT + Swagger]
end
subgraph ApplicationLayer["Application Layer"]
UC[Use Cases<br/>ProcessDocumentUseCase<br/>ExportExcelUseCase<br/>ImportExcelUseCase]
Services[Services S23<br/>ValidationService<br/>SearchService<br/>StatisticsService<br/>ManualEntryService<br/>DedupConfigService]
DTOs[DTOs frozen<br/>ProcessDocument Req/Resp<br/>Statistics Resp<br/>SearchDocuments Req/Resp<br/>ValidateDocument Req/Resp<br/>CreateManualDocument Req/Resp<br/>RecurringCheck Req/Resp<br/>DedupConfig Resp/UpdateReq]
Container[Service Container<br/>14 properties lazy-loaded<br/>Dependency Injection<br/>Thread-safe RLock]
end
subgraph DomainLayer["Domain Layer (Core)"]
Entities[Entities<br/>Document<br/>DocumentType<br/>DocumentStatus]
ValueObjects[Value Objects<br/>EntityResult<br/>OCRResult<br/>DuplicateCandidate<br/>NERFields<br/>CSJMetadata<br/>ProvisionalInfo<br/>SectionsInfo]
Errors[core/errors.py<br/>AppError + ErrorCode<br/>Tipado de errores S27]
end
subgraph InfrastructureLayer["Infrastructure Layer (Adapters)"]
subgraph "OCR Adapters"
OCRRouter[OCR Router<br/>Confidence-based]
Tesseract[Tesseract Adapter]
Paddle[PaddleOCR Adapter]
QAnalyzer[Quality Analyzer]
end
subgraph "NER Adapters"
SpaCy[SpaCy Adapter<br/>es_core_news_lg]
CtxScorer[ContextScorer ✅]
EntLinker[EntityLinker ✅]
RadVal[RadicadoValidator ✅]
StructAnalyzer[DocumentStructureAnalyzer ✅]
NERensemble[EntityExtractorEnsemble ✅]
ActiveLearn[ActiveLearning ✅<br/>Infraestructura]
ActaReparto[ActaRepartoExtractor ✅]
SectionsDet[SectionsDetector ✅]
ProvMeasure[ProvisionalMeasureDetector ✅]
end
subgraph "Duplicate Detection"
DedupEnsemble[Hybrid Ensemble<br/>Detector]
ExactHash[Exact Matcher<br/>SHA-256]
MinHash[MinHash LSH<br/>Matcher]
TFIDF[TF-IDF Similarity<br/>Matcher]
EntityMatch[Entity Matcher<br/>Legal Parties]
end
subgraph "Persistence"
Repo[SQLite Repository<br/>FTS5 Support<br/>3 Mixins: Connection,<br/>Correction, Metrics<br/>Transaction Support]
ISP[ISP Protocols:<br/>DocumentReader,<br/>DocumentWriter + Txn,<br/>DocumentSearcher,<br/>CorrectionRepository,<br/>MetricsRepository]
end
subgraph "Logging"
Logger[Logger Service<br/>File + DB handlers]
DBHandler[Database Handler<br/>Audit Trail]
end
end
subgraph PortsLayer["Ports (Protocols/Interfaces)"]
OCRPort[OCRPort Protocol]
NERPort[NER Port Protocol]
DedupPort[DuplicateDetectorPort]
RepoPort[DocumentRepository<br/>Protocol ISP:<br/>Reader, Writer, Searcher,<br/>Correction, Metrics]
end
GUI --> Container
GUI --> Services
CLI --> Container
API --> Container
API --> Services
Container --> UC
Container --> Services
UC --> DTOs
UC --> OCRPort
UC --> NERPort
UC --> DedupPort
UC --> RepoPort
Services --> RepoPort
Services --> DTOs
DTOs --> Entities
DTOs --> ValueObjects
UC --> Errors
OCRPort -.implements.-> OCRRouter
OCRRouter --> Tesseract
OCRRouter --> Paddle
OCRRouter --> QAnalyzer
NERPort -.implements.-> SpaCy
SpaCy --> CtxScorer
SpaCy --> EntLinker
SpaCy --> RadVal
SpaCy --> StructAnalyzer
NERPort -.implements.-> NERensemble
SpaCy --> ActiveLearn
SpaCy --> ActaReparto
SpaCy --> SectionsDet
SpaCy --> ProvMeasure
DedupPort -.implements.-> DedupEnsemble
DedupEnsemble --> ExactHash
DedupEnsemble --> MinHash
DedupEnsemble --> TFIDF
DedupEnsemble --> EntityMatch
RepoPort -.implements.-> ISP
ISP -.implements.-> Repo
OCRRouter -.logs.-> Logger
SpaCy -.logs.-> Logger
DedupEnsemble -.logs.-> Logger
Logger --> DBHandler
DBHandler --> Repo
style DomainLayer fill:#d4f1d4
style ApplicationLayer fill:#d4e4f7
style InfrastructureLayer fill:#fff4d4
style InterfacesLayer fill:#f7d4e4
style PortsLayer fill:#e4d4f7
Descripción de Capas¶
1. Domain Layer (Núcleo)¶
Responsabilidad: Lógica de negocio pura, sin dependencias externas.
- Entities:
Document,DocumentType,DocumentStatus - Value Objects:
EntityResult,OCRResult,DuplicateCandidate,NERFields,CSJMetadata,ProvisionalInfo,SectionsInfo(Sprint 23: 4 nuevos frozen VOs) - Errors (Sprint 27):
core/errors.py—AppErrordataclass +ErrorCodeenum con mapeo a HTTP status codes. Base del flujo de errores tipados Result[T, AppError] - Reglas de negocio: Validaciones de estado, invariantes del dominio
Característica clave: Cero dependencias a frameworks o bibliotecas externas.
2. Application Layer¶
Responsabilidad: Orquestación de casos de uso, coordinación entre puertos.
- Use Cases:
ProcessDocumentUseCase(pipeline OCR -> NER -> Dedup -> Persist),ExportExcelUseCase,ImportExcelUseCase - Services (Sprint 23):
ValidationService,SearchService,StatisticsService,ManualEntryService,DedupConfigService— lógica extraída de GUI, sin dependencia Streamlit - DTOs: Objetos inmutables para transferencia entre capas
- Service Container: Inyección de dependencias, 14 properties lazy-loaded, thread-safe con RLock
Pattern: Railway-Oriented Programming con returns.Result para manejo de errores.
3. Infrastructure Layer (Adapters)¶
Responsabilidad: Implementaciones concretas de puertos, integraciones externas.
- OCR Adapters: Tesseract, PaddleOCR con routing inteligente
- NER Adapters: SpaCy con modelo
es_core_news_lg+ Validators (ContextScorer, EntityLinker, RadicadoValidator, DocumentStructureAnalyzer) + Extractores especializados (ActaRepartoExtractor, SectionsDetector, ProvisionalMeasureDetector) - Duplicate Detection: 4 niveles (Exact, MinHash, TF-IDF, Entity)
- Persistence: SQLite con FTS5 para búsqueda full-text, refactorizado en 3 mixins (Connection, Correction, Metrics)
- Logging: Sistema centralizado con handlers de archivo (rotación diaria) y base de datos (auditoría)
- Config Persistence: EnsembleConfig con save/load JSON (data/config/dedup_config.json)
Pattern: Adapter Pattern, cada implementación cumple un Port (protocolo ABC).
4. Interfaces Layer¶
Responsabilidad: Interacción con usuarios (GUI, CLI, API REST).
- Streamlit GUI ✅: Interfaz web para operadores (9 páginas implementadas)
- CLI ✅: Interfaz de línea de comandos con 20 comandos (procesamiento batch, búsqueda, exportación, debugging) en
interfaces/cli.py - API REST ✅: FastAPI con 22 endpoints, JWT auth (HS256, 8h expiry, max 5 refreshes), Swagger UI (
/docs), async OCR via TaskManager (ThreadPoolExecutor), exception handler global. Score auditoría 9.2/10. Componentes:main.py(app factory),config.py(ApiSettings),auth.py(JWT),dependencies.py(DI),result_mapper.py(Result→JSON),tasks.py(async), 11 routers enroutes/, 8 schemas Pydantic enschemas/
5. Ports (Protocolos)¶
Responsabilidad: Contratos (interfaces) que define Application, implementa Infrastructure.
OCRPort: Contrato para motores OCRNERPort: Contrato para extractores de entidadesDuplicateDetectorPort: Contrato para detectores de duplicadosDocumentRepository: Contrato para persistencia, segregado en ISP protocols:DocumentReader,DocumentWriter,DocumentSearcher,CorrectionRepository,MetricsRepository
Flujo de Dependencias¶
Regla de Oro: Las capas internas nunca conocen las externas. Infrastructure depende de Application, pero no al revés.
Nota: Los 16 bypasses legacy GUI→Repository fueron eliminados en Sprint 23.1 (Fase 1.5). La API REST no tiene ningún bypass — todas las operaciones pasan por Services/UseCases del Application Layer.
Flujo de Errores Tipados (Sprint 27)¶
AppError con ErrorCode¶
A partir del Sprint 27, el sistema utiliza un flujo de errores tipado basado en AppError con ErrorCode que se propaga desde la capa de persistencia hasta la capa API de forma uniforme:
Persistence Layer Application Layer API Layer
───────────────── ─────────────────── ──────────────
SQLiteRepository Services / UseCases result_mapper.py
│ │ │
├─ Result[T, AppError] ───→ ├─ bind/map (ROP) ───────→ ├─ map_result_to_response()
│ │ │
│ AppError( │ Propaga AppError │ AppError.code.http_status
│ code=ErrorCode.XXX, │ sin transformar o │ → HTTP status code directo
│ message="...", │ enriquece con contexto │
│ details={...} │ │ Ej: NOT_FOUND → 404
│ ) │ │ VALIDATION_ERROR → 422
│ │ │ DUPLICATE → 409
│ INTERNAL_ERROR → 500
Propagación por capas¶
-
Persistence → Application: Los métodos del repositorio (
delete(),update_status(),update_document_field(),save_processing_metrics()) retornanResult[T, AppError]. La capa de aplicación usabind()ymap()del patrón ROP para encadenar operaciones sin try/except. -
Application → API: Los Services y UseCases retornan
Result[T, AppError]a los routers. No se transforman las excepciones — el error fluye como valor. -
API → HTTP Response:
result_mapper.pyconvierte el resultado: - AppError (preferido): Lee
AppError.code.http_statuspara obtener el código HTTP directamente. ElErrorCodeenum define el mapeo (ej:ErrorCode.NOT_FOUND→ 404,ErrorCode.VALIDATION_ERROR→ 422). - String errors (legacy): Para compatibilidad con código anterior que retorna
Failure("mensaje string"), el mapper aplica heurísticas de clasificación (busca patrones como "not found", "invalid", etc.) para determinar el HTTP status code.
Ejemplo de flujo completo¶
# Persistence (retorna Result)
def delete(self, doc_id: str) -> Result[None, AppError]:
...
return Failure(AppError(code=ErrorCode.NOT_FOUND, message=f"Documento {doc_id} no encontrado"))
# Application (propaga Result)
def delete_document(self, doc_id: str) -> Result[None, AppError]:
return self.repository.delete(doc_id)
# API Router (usa result_mapper)
@router.delete("/{doc_id}")
def delete_document(...):
result = container.repository.delete(doc_id)
return map_result_to_response(result) # → 404 {"error": "Documento X no encontrado"}
Principios Aplicados¶
- Dependency Inversion Principle (DIP): Application depende de abstracciones (Ports), no implementaciones.
- Single Responsibility Principle (SRP): Cada adapter tiene una responsabilidad única.
- Open/Closed Principle (OCP): Extender con nuevos adapters sin modificar Application.
- Interface Segregation Principle (ISP): Ports mínimos, específicos por caso de uso.
Leyenda de Estados¶
| Símbolo | Significado |
|---|---|
| ✅ | Implementado y funcional |
| ⏳ | Planificado para fase futura |
| 🔬 | En investigación |
Última actualización: 2026-03-27 (Sprint 28: flujo errores tipados AppError+ErrorCode, Result[T, AppError] en persistencia, 1707 tests, 87.77% coverage gate 88%, tests/integration/ 16 tests cross-layer)