184 lines
6.4 KiB
Python
184 lines
6.4 KiB
Python
# core/affidavit.py
|
|
from datetime import datetime
|
|
from typing import Any, Dict, Optional, List
|
|
import hashlib
|
|
import json
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.modules.affidavit_logs.models import Operation, StatusOP, AffidavitRecord
|
|
|
|
|
|
class LegalAffidavit:
|
|
"""
|
|
Certificador Legal Digital
|
|
Cada operación genera un acta notarial inmutable
|
|
"""
|
|
|
|
def __init__(self, db: Session, user, operation: str, client_id: int = None):
|
|
self.db = db
|
|
self.user = user
|
|
self.operation = operation
|
|
self.client_id = client_id
|
|
self.timestamp = datetime.utcnow()
|
|
self.entities_affected: List[Dict] = []
|
|
self.rules_validated: List[Dict] = []
|
|
self.evidence: List[Dict] = []
|
|
self._record = None
|
|
|
|
def _get_operation_enum(self) -> Operation:
|
|
"""Convierte string de operación a Enum Operation"""
|
|
operation_map = {
|
|
"create": Operation.CREATED,
|
|
"CREATE": Operation.CREATED,
|
|
"update": Operation.UPDATED,
|
|
"UPDATE": Operation.UPDATED,
|
|
"delete": Operation.DELETED,
|
|
"DELETE": Operation.DELETED,
|
|
"cancel": Operation.CANCELED,
|
|
"CANCEL": Operation.CANCELED,
|
|
"tried": Operation.TRIED,
|
|
"TRIED": Operation.TRIED,
|
|
}
|
|
return operation_map.get(self.operation.lower(), Operation.TRIED)
|
|
|
|
def _get_status_enum(self, success: bool) -> StatusOP:
|
|
"""Convierte resultado a StatusOP"""
|
|
if success:
|
|
return StatusOP.COMPLETED
|
|
return StatusOP.INVALID
|
|
|
|
def _hash_data(self, data: Any) -> str:
|
|
"""Genera hash para integridad de datos"""
|
|
data_str = json.dumps(data, sort_keys=True, default=str)
|
|
return hashlib.sha256(data_str.encode()).hexdigest()
|
|
|
|
def certify_entity(self, entity_type: str, entity_id: int, entity_data: Dict) -> 'LegalAffidavit':
|
|
"""Certifica que una entidad fue afectada"""
|
|
self.entities_affected.append({
|
|
"type": entity_type,
|
|
"id": entity_id,
|
|
"data_hash": self._hash_data(entity_data),
|
|
"timestamp": self.timestamp.isoformat()
|
|
})
|
|
return self
|
|
|
|
def validate_rule(self, rule_name: str, rule_result: bool, rule_details: str = "") -> 'LegalAffidavit':
|
|
"""Valida una regla de negocio"""
|
|
self.rules_validated.append({
|
|
"rule": rule_name,
|
|
"passed": rule_result,
|
|
"details": rule_details,
|
|
"validated_at": self.timestamp.isoformat()
|
|
})
|
|
|
|
if not rule_result:
|
|
raise AffidavitValidationError(f"Regla '{rule_name}' no cumplida: {rule_details}")
|
|
|
|
return self
|
|
|
|
def add_evidence(self, evidence_type: str, evidence_data: Any) -> 'LegalAffidavit':
|
|
"""Agrega evidencia digital"""
|
|
self.evidence.append({
|
|
"type": evidence_type,
|
|
"data": evidence_data,
|
|
"hash": self._hash_data(evidence_data),
|
|
"timestamp": self.timestamp.isoformat()
|
|
})
|
|
return self
|
|
|
|
def _generate_signature(self) -> str:
|
|
"""Genera firma digital del acta"""
|
|
data_to_sign = f"{self.user.id}{self.timestamp.isoformat()}{self.operation}{json.dumps(self.entities_affected, sort_keys=True)}"
|
|
return hashlib.sha512(data_to_sign.encode()).hexdigest()
|
|
|
|
def _build_description(self) -> str:
|
|
"""Construye la descripción del acta"""
|
|
entities = ", ".join([f"{e['type']}:{e['id']}" for e in self.entities_affected])
|
|
rules = ", ".join([r['rule'] for r in self.rules_validated if r['passed']])
|
|
|
|
desc = f"Operación {self.operation} por usuario {self.user.email}"
|
|
if entities:
|
|
desc += f" | Entidades: {entities}"
|
|
if rules:
|
|
desc += f" | Reglas: {rules}"
|
|
return desc
|
|
|
|
def sign(self, success: bool = True) -> Dict:
|
|
"""
|
|
Firma y guarda el acta notarial en la base de datos
|
|
"""
|
|
description = self._build_description()
|
|
signature = self._generate_signature()
|
|
status = self._get_status_enum(success)
|
|
operation_enum = self._get_operation_enum()
|
|
|
|
# Crear registro en BD
|
|
self._record = AffidavitRecord(
|
|
operation=operation_enum,
|
|
user_id=self.user.id,
|
|
client_id=self.client_id,
|
|
description=description,
|
|
adjustment=json.dumps(self.evidence) if self.evidence else None,
|
|
status=status,
|
|
sign=signature,
|
|
created_at=self.timestamp,
|
|
created_by=self.user.id
|
|
)
|
|
|
|
self.db.add(self._record)
|
|
self.db.commit()
|
|
self.db.refresh(self._record)
|
|
|
|
return {
|
|
"affidavit_id": self._record.id,
|
|
"operation": self.operation,
|
|
"user": {
|
|
"id": self.user.id,
|
|
"email": self.user.email,
|
|
"user_type": self.user.user_type,
|
|
"rol_operativo": self.user.rol_operativo
|
|
},
|
|
"timestamp": self.timestamp.isoformat(),
|
|
"description": description,
|
|
"status": status.value,
|
|
"signature": signature,
|
|
"entities_affected": self.entities_affected,
|
|
"rules_validated": self.rules_validated
|
|
}
|
|
|
|
def get_record_id(self) -> Optional[int]:
|
|
"""Retorna el ID del registro creado"""
|
|
return self._record.id if self._record else None
|
|
|
|
|
|
class AffidavitValidationError(Exception):
|
|
"""Error de validación notarial"""
|
|
pass
|
|
|
|
|
|
def certify_operation(operation_name: str):
|
|
"""Decorador para certificar operaciones automáticamente"""
|
|
def decorator(func):
|
|
async def wrapper(*args, **kwargs):
|
|
db = kwargs.get('db')
|
|
user = kwargs.get('current_user')
|
|
client_id = kwargs.get('client_id')
|
|
|
|
if not db and args and hasattr(args[0], 'db'):
|
|
db = args[0].db
|
|
|
|
if db and user:
|
|
affidavit = LegalAffidavit(db, user, operation_name, client_id)
|
|
kwargs['affidavit'] = affidavit
|
|
|
|
try:
|
|
result = await func(*args, **kwargs)
|
|
affidavit.sign(success=True)
|
|
return result
|
|
except Exception as e:
|
|
affidavit.sign(success=False)
|
|
raise
|
|
|
|
return await func(*args, **kwargs)
|
|
return wrapper
|
|
return decorator |