Documents Lifecycle¶
Servicios del ciclo de vida de documentos: creacion, edicion, eliminacion y rechazo.
Ubicacion: services/documents/lifecycle/
Flujo de Estados¶
create_document()
|
v
[ draft ] <------- save_document_changes()
| ^
| |
start_signing() reject / edit
| |
v |
[ sent_to_sign ] -------> [ rejected ]
|
sign_document()
|
v
[ signed ]
Creacion (lifecycle/creation.py)¶
create_document()¶
def create_document(
document_type_acronym: str,
reference: str,
creator_id: str,
*,
schema_name: str,
recipients: Optional[Dict] = None, # Solo para NOTA
sender_sector_id: Optional[str] = None, # Solo para NOTA
auth_source: Optional[str] = None
) -> Dict[str, Any]:
Flujo:
- Validar inputs (acronimo, referencia, UUID del creador)
- Detectar si es tipo NOTA (validar recipients si aplica)
- Obtener
document_type_iddesde BD - Generar UUID para el documento
- INSERT en
document_draftcon estadodraft - Asignar creador como firmante numerador (signing_order=1)
- Si es NOTA con recipients: guardar en misma transaccion
- Commit y retornar resultado
Reglas especiales para NOTA:
| Escenario | Comportamiento |
|---|---|
| NOTA con recipients | Valida y guarda TO/CC/BCC |
| NOTA sin recipients | Valido (se agregan despues con save) |
| No-NOTA con recipients | Ignora con warning en log |
Contexto de auditoria:
# Inyecta metadata para triggers de BD
set_audit_context(cursor, user_id=creator_id, auth_source=auth_source)
Edicion (lifecycle/editing.py)¶
get_document_details_for_editing()¶
Obtiene datos completos del documento para la pantalla de edicion.
Datos incluidos en la respuesta:
- Datos basicos: referencia, contenido HTML, estado, tipo
- Firmantes con sello, departamento, foto de perfil
- Info de rechazo (si aplica)
- PDF URL (si es documento importado y existe en R2)
- Recipients (si es tipo NOTA)
- Expedientes propuestos
save_document_changes()¶
Guarda cambios en un documento existente. Soporta actualizacion parcial.
def save_document_changes(
document_id: str,
reference: Optional[str] = None,
content: Optional[str] = None,
signers: Optional[List[Dict]] = None,
recipients: Optional[Dict] = None,
sender_sector_id: Optional[str] = None,
proposed_case_ids: Optional[List[str]] = None,
user_id: Optional[str] = None,
*,
schema_name: str
) -> Dict[str, Any]:
Operaciones atomicas en transaccion:
| Campo | Operacion |
|---|---|
reference |
UPDATE document_draft.reference |
content |
UPDATE document_draft.content (formato {"html": "..."}) |
signers |
DELETE + INSERT en document_signers (reescritura completa) |
recipients |
DELETE + INSERT en note_recipients (solo NOTA) |
proposed_case_ids |
DELETE + INSERT en case_proposed_documents (deduplicados) |
Deduplicacion
Los proposed_case_ids se deduplican automaticamente con set() para prevenir vinculaciones duplicadas. Se registra en case_history solo para expedientes nuevos.
check_document_can_be_edited()¶
Verifica sin lanzar excepciones (retorna dict con can_edit: bool).
Eliminacion (lifecycle/deletion.py)¶
delete_document()¶
Soft delete: marca is_deleted = true sin borrar fisicamente.
Validaciones:
| Validacion | Error |
|---|---|
| Documento no existe | DocumentNotFoundError |
| Ya eliminado | ConflictError |
| No es el creador | AuthorizationError |
| Estado no permitido | DocumentStateError |
Estados eliminables: draft, rejected
Proceso:
- Validar formato de UUIDs
- Obtener documento y verificar condiciones
- Limpiar PDF de R2 (soft-fail, no bloquea si falla)
- En transaccion: desvincular de expedientes + soft delete
- Retornar resultado con info de cleanup
# La limpieza de PDF no bloquea el proceso
pdf_cleanup_info = _cleanup_pdf_from_r2(document_id, schema_name=schema_name)
# Retorna: {"attempted": True, "success": True/False, "note": "..."}
Rechazo (lifecycle/rejection.py)¶
reject_document()¶
Rechaza un documento con motivo. Revierte el estado para edicion.
def reject_document(
document_id: str, user_id: str, reason: str, *, schema_name: str
) -> Dict[str, Any]:
Quien puede rechazar:
| Rol | Permitido |
|---|---|
| Creador del documento | Si |
| Firmante asignado | Si |
| Otro usuario | No (403) |
Operaciones en transaccion:
UPDATE document_draft SET status = 'rejected', sent_to_sign_at = NULL, sent_by = NULLINSERT INTO document_rejectionsUPDATE document_signers SET status = 'rejected' WHERE signed_at IS NULL- Limpieza de PDF en R2 (soft-fail)
Consultas adicionales:
| Funcion | Proposito |
|---|---|
get_document_rejections() |
Historial de rechazos de un documento |
get_rejected_documents_for_user() |
Documentos rechazados donde el usuario participa |
can_user_reject_document() |
Verificacion sin excepciones |