Saltar a contenido

Sistema de errores

Gestión Civis gestiona los errores de negocio mediante una excepción aplicativa única (AppException) y un manejador global que la convierte en una respuesta JSON homogénea. Cada módulo define su catálogo de errores con códigos canónicos.

Regla del proyecto

Usar siempre raise AppException(...) (a través del catálogo errors.py del módulo). Nunca devolver el error con return jsonify({"error": ...}).

Componentes

Definidos en app/core/exceptions.py.

ErrorSource

Captura automática del punto de origen del error.

@dataclass
class ErrorSource:
    file: str   # archivo donde se lanzó
    line: int   # número de línea
    fn: str     # función

AppError

Estructura de datos del error.

@dataclass
class AppError:
    code: str                       # código canónico (ej. "REG-300")
    message: str                    # mensaje para el usuario
    source: Optional[ErrorSource]   # contexto de origen
    detail: Optional[Dict]          # datos adicionales (severity, campos…)
    http_status: int = 400          # código HTTP

AppException

Excepción que se lanza desde el código de negocio.

class AppException(Exception):
    def __init__(self, code, message, *, http_status=400, detail=None, source_stack_level=2):
        self.app_error = AppError(
            code=code, message=message, http_status=http_status,
            detail=detail, source=build_source(stack_level=source_stack_level),
        )
  • No produce la respuesta HTTP por sí misma: solo transporta el error.
  • Captura automáticamente archivo, línea y función de origen.

Manejador global

Registrado en la app factory (app/__init__.py):

@app.errorhandler(AppException)
def handle_app_exception(error: AppException):
    ae = error.app_error
    payload = {
        "error": {
            "code": ae.code,
            "message": ae.message,
            "source": {"file": ae.source.file, "line": ae.source.line, "fn": ae.source.fn}
                       if ae.source else None,
            "detail": ae.detail,
        }
    }
    return jsonify(payload), ae.http_status

Formato de respuesta de error

{
  "error": {
    "code": "REG-300",
    "message": "El registro no admite esta operación en su estado actual.",
    "source": { "file": ".../registro/routes.py", "line": 870, "fn": "anular_registro" },
    "detail": { "severity": "warning", "estado": "ANULADO" }
  }
}

Otros manejadores

La factoría también gestiona RequestEntityTooLarge (subida > 10 MB → 413 con un mensaje en español).

Catálogos de errores por módulo

Cada módulo define errors.py con códigos agrupados por rango. El patrón: cada error es un staticmethod que devuelve una AppException lista para lanzar.

# app/modules/registro/servicios/errors.py (resumen)
class RegistroErrors:
    @staticmethod
    def REG_003():
        return AppException("REG-003", "El extracto supera los 240 caracteres.",
                            http_status=400, detail={"severity": "warning"})

Uso en el handler:

if len(extracto) > 240:
    raise RegistroErrors.REG_003()

Convención de rangos (ejemplo: módulo Registro)

Rango Categoría
REG-0xx Validación de entrada / formato.
REG-1xx Numeración / contador / hash-chain.
REG-2xx Adjuntos (tamaño, hash, antivirus).
REG-3xx Estado / transiciones.
REG-4xx Asignación / expediente-almacén / traspaso UD.
REG-5xx Oficinas.
REG-6xx SIR.
REG-7xx Sede ciudadana.
REG-9xx Genéricos / no encontrado.

Prefijo por módulo

El prefijo del código identifica el módulo (REG-, DOC-, PRES-, TES-, REC-, CONT-…), lo que facilita localizar el origen del error.