from app import db from datetime import datetime import json class MoodleSyncTask(db.Model): """ Cola persistente de eventos y tareas de sincronización asíncrona hacia Moodle 4.1. Garantiza tolerancia a fallos ante caídas o saturación del servidor Moodle, incorporando reintentos exponenciales y Dead Letter Queue (DLQ). """ __tablename__ = 'moodle_sync_tasks' id = db.Column(db.Integer, primary_key=True) action = db.Column(db.String(50), nullable=False, index=True) # CREATE_USER, UPDATE_USER, ENROL_USER, UNENROL_USER, ASSIGN_ROLE entity_type = db.Column(db.String(50), nullable=False) # user, commission, enrollment entity_id = db.Column(db.String(100), nullable=True) _payload = db.Column('payload', db.Text, nullable=False) # JSON serializado en Text status = db.Column(db.String(20), default='PENDING', index=True) # PENDING, PROCESSING, RETRYING, COMPLETED, FAILED attempts = db.Column(db.Integer, default=0) max_attempts = db.Column(db.Integer, default=5) error_message = db.Column(db.Text, nullable=True) next_retry_at = db.Column(db.DateTime, default=datetime.utcnow, index=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) def __init__(self, **kwargs): if 'payload' in kwargs: raw_payload = kwargs.pop('payload') if isinstance(raw_payload, (dict, list)): kwargs['_payload'] = json.dumps(raw_payload) else: kwargs['_payload'] = str(raw_payload or '{}') super().__init__(**kwargs) @property def payload(self): try: return json.loads(self._payload) if self._payload else {} except Exception: return {} @payload.setter def payload(self, val): if isinstance(val, (dict, list)): self._payload = json.dumps(val) else: self._payload = str(val or '{}') def get_payload_dict(self): return self.payload def to_dict(self): return { 'id': self.id, 'action': self.action, 'entity_type': self.entity_type, 'entity_id': self.entity_id, 'payload': self.payload, 'status': self.status, 'attempts': self.attempts, 'max_attempts': self.max_attempts, 'error_message': self.error_message, 'next_retry_at': self.next_retry_at.isoformat() if self.next_retry_at else None, 'created_at': self.created_at.isoformat() if self.created_at else None, 'updated_at': self.updated_at.isoformat() if self.updated_at else None } def __repr__(self): return f''