# 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