Skip to content

Manual Técnico ​

Subsistema informático de soporte para el proceso de selección de postulantes al curso de formación y especialización judicial.

Exportar a PDF

Esta página reúne todo el manual. Use Ctrl + P (Cmd + P en macOS) y elija Guardar como PDF.

Guía de Integración OAuth2 y OpenID Connect ​

Esta sección está orientada a desarrolladores que necesitan implementar login con el proveedor de identidad:

  • Aplicaciones web (frontend + backend).
  • Aplicaciones backend que consumen APIs protegidas.
  • Integraciones que requieren identidad de usuario mediante OIDC.

Qué resuelve esta guía ​

  • Cómo implementar el flujo de autorización recomendado (authorization_code + PKCE).
  • Cómo intercambiar code por tokens y consultar userinfo.
  • Cómo validar, revocar y cerrar sesión de forma segura.
  • Cómo consumir la API de directorio con token OAuth2.

Estándares de referencia ​

  • OAuth 2.0 (RFC 6749): modelo de autorización y tipos de grant.
  • PKCE (RFC 7636): protección del flujo de código de autorización.
  • OpenID Connect Core 1.0: capa de identidad sobre OAuth2.
  • OpenID Connect Discovery 1.0: descubrimiento automático de metadatos.
  • OAuth 2.0 Token Introspection (RFC 7662): validación de tokens.
  • OAuth 2.0 Token Revocation (RFC 7009): revocación de tokens.
  • JSON Web Key (RFC 7517) y JWT (RFC 7519): verificación de firmas y estructura de tokens.

Ruta sugerida de lectura ​

  1. Introducción y decisiones de arquitectura.
  2. Discovery OIDC y configuración de cliente.
  3. Flujo de login paso a paso con ejemplos HTTP.
  4. Consumo de endpoints de negocio (/api/v1/directory/users/*).

Introducción ​

Base URL por entorno ​

En este módulo se usa BASE_URL_IDP como referencia común para todos los endpoints.

  • Entorno de test: https://backend.idp-test.eje.gob.bo
  • Entorno de producción: https://backend.idp.eje.gob.bo

Regla de uso:

  • Reemplazar BASE_URL_IDP por la URL del entorno objetivo (test o producción).

Esta guía explica cómo integrar autenticación de usuarios con OAuth2 y OpenID Connect (OIDC) contra:

  • Discovery OIDC: BASE_URL_IDP/.well-known/openid-configuration
  • Documentación API: BASE_URL_IDP/api/docs

Cuándo usar OAuth2 y cuándo OIDC ​

  • Usa OAuth2 cuando tu objetivo principal es autorización para consumir una API con access_token.
  • Usa OIDC cuando, además de autorización, necesitas identidad de usuario autenticado (id_token y/o userinfo).

En login de aplicaciones web, normalmente se usan ambos: OAuth2 para tokens de acceso y OIDC para identidad.

Flujo recomendado para login ​

Para aplicaciones web y SPA con backend, se recomienda:

  1. authorization_code.
  2. PKCE (S256).
  3. Intercambio de código en backend o BFF (evitar exponer secretos en frontend).
  4. Sesión de aplicación basada en cookie segura (HttpOnly, Secure, SameSite).

Roles del protocolo ​

  • Authorization Server / IdP: emite tokens y autentica usuarios.
  • Client: tu aplicación (frontend, backend o BFF).
  • Resource Server: APIs protegidas que validan tokens.
  • Resource Owner: usuario final.

Datos que debe definir el equipo integrador ​

  • scope mínimo requerido (openid, profile, email y, si aplica, scopes de directorio oficiales).

Referencias normativas ​

  • OAuth 2.0: RFC 6749.
  • PKCE: RFC 7636.
  • OpenID Connect Core 1.0.
  • OpenID Connect Discovery 1.0.

Discovery OAuth2/OIDC ​

Fuente: BASE_URL_IDP/.well-known/openid-configuration

El discovery permite que el cliente no tenga endpoints hardcodeados y obtenga metadatos oficiales del proveedor.

Metadatos principales ​

  • issuer: BASE_URL_IDP
  • authorization_endpoint: BASE_URL_IDP/auth/public/authorize/
  • token_endpoint: BASE_URL_IDP/oauth2/token
  • userinfo_endpoint: BASE_URL_IDP/oidc/userinfo
  • jwks_uri: BASE_URL_IDP/.well-known/jwks.json
  • introspection_endpoint: BASE_URL_IDP/oauth2/introspect
  • revocation_endpoint: BASE_URL_IDP/oauth2/revoke
  • end_session_endpoint: BASE_URL_IDP/auth/logout/client

Cómo consumir discovery en runtime ​

  1. Cargar el documento de discovery al iniciar la aplicación o en bootstrap de autenticación.
  2. Cachear metadatos en memoria con TTL corto (por ejemplo, 5 a 15 minutos).
  3. Usar exclusivamente endpoints publicados por discovery para reducir desalineaciones por cambios del proveedor.

Ejemplo:

bash
curl -sS 'BASE_URL_IDP/.well-known/openid-configuration'

Capacidades declaradas ​

  • response_types_supported: code
  • response_modes_supported: query
  • grant_types_supported:
    • authorization_code
    • refresh_token
    • client_credentials
  • code_challenge_methods_supported: S256
  • id_token_signing_alg_values_supported: RS256
  • token_endpoint_auth_methods_supported:
    • client_secret_post
    • client_secret_basic

Interpretación práctica:

  • response_types_supported: code confirma que el login debe implementarse con código de autorización.
  • code_challenge_methods_supported: S256 confirma uso de PKCE en modo seguro.
  • id_token_signing_alg_values_supported: RS256 indica que la verificación de firma usa clave pública RSA (JWKS).
  • token_endpoint_auth_methods_supported define cómo autenticar cliente en /oauth2/token (según tipo de cliente).

Scopes soportados ​

  • openid
  • profile
  • email
  • directory:all:users:read
  • directory:external:users:read
  • directory:internal:users:read
  • directory:admin:users:read

Recomendación:

  • Solicitar solo scopes mínimos por principio de mínimo privilegio.
  • Para login base, comenzar con: openid profile email.
  • Agregar scopes de directorio solo cuando el caso de uso lo requiera.
  • El sistema puede definir scopes adicionales para el acceso a datos específicos de Identidad, como los datos de ciudadanía.
  • El sistema puede definir scopes dinámicos para la incorporación de nuevos servidores de recursos que deleguen autorización a la plataforma de identidad.
  • Para consultar la existencia y forma de uso de scopes adicionales consultar al equipo de informática de la Escuela de Jueces del Estado.

Claims soportados ​

  • sub
  • name
  • given_name
  • family_name
  • email
  • nro_documento_identidad
  • tipo_documento_identidad
  • amr
  • acr
  • auth_time
  • nonce
  • at_hash
  • iat
  • exp
  • iss
  • aud
  • tipo_usuario
  • is_eje_sysadmin

Validación de tokens y firma ​

Para id_token o JWT firmados con RS256:

  1. Obtener llaves en jwks_uri.
  2. Buscar la llave por kid del header del token.
  3. Verificar firma y validar claims críticos: iss, aud, exp, iat, nonce (si aplica).

Esto sigue OIDC Core y los RFC 7517/7519.

Endpoints OAuth2/OIDC ​

Los siguientes endpoints provienen del discovery OIDC del proveedor de identidad.

GrupoMétodoRutaUso principal
AutorizaciónGET/auth/public/authorize/Iniciar flujo OAuth2/OIDC (authorization code).
TokensPOST/oauth2/tokenIntercambiar code por tokens o renovar con refresh_token.
UserInfoGET/oidc/userinfoObtener claims del usuario autenticado.
IntrospecciónPOST/oauth2/introspectValidar estado y metadatos de un access token.
RevocaciónPOST/oauth2/revokeRevocar access token o refresh token.
Claves públicasGET/.well-known/jwks.jsonObtener llaves públicas para verificación de JWT.
Cierre de sesiónGET/POST/auth/logout/clientFinalizar sesión en el proveedor de identidad.

Flujo de integración de login (Authorization Code + PKCE) ​

Paso 1: redirección a autorización ​

Endpoint: GET /auth/public/authorize/

Parámetros mínimos:

  • response_type=code
  • client_id=<CLIENT_ID>
  • redirect_uri=<URI_REGISTRADA>
  • scope=openid profile email
  • state=<ALEATORIO>
  • code_challenge=<PKCE_CHALLENGE>
  • code_challenge_method=S256

Ejemplo:

text
BASE_URL_IDP/auth/public/authorize/?response_type=code&client_id=mi_cliente&redirect_uri=https%3A%2F%2Fapp.midominio.com%2Fauth%2Fcallback&scope=openid%20profile%20email&state=RANDOM123&code_challenge=abc123...&code_challenge_method=S256

Paso 2: intercambio de código por tokens ​

Endpoint: POST /oauth2/token

Content-Type esperado: application/x-www-form-urlencoded

Payload base:

  • grant_type=authorization_code
  • code=<AUTHORIZATION_CODE>
  • redirect_uri=<URI_REGISTRADA>
  • client_id=<CLIENT_ID>
  • code_verifier=<PKCE_VERIFIER>

Ejemplo:

bash
curl -X POST 'BASE_URL_IDP/oauth2/token' \
	-H 'Content-Type: application/x-www-form-urlencoded' \
	--data-urlencode 'grant_type=authorization_code' \
	--data-urlencode 'client_id=mi_cliente' \
	--data-urlencode 'code=AUTH_CODE' \
	--data-urlencode 'redirect_uri=https://app.midominio.com/auth/callback' \
	--data-urlencode 'code_verifier=PKCE_VERIFIER'

Paso 3: obtención de identidad de usuario ​

Endpoint: GET /oidc/userinfo

Header:

  • Authorization: Bearer <access_token>

Ejemplo:

bash
curl 'BASE_URL_IDP/oidc/userinfo' \
	-H 'Authorization: Bearer ACCESS_TOKEN'

Paso 4: renovación de token ​

Endpoint: POST /oauth2/token

Payload:

  • grant_type=refresh_token
  • refresh_token=<REFRESH_TOKEN>
  • client_id=<CLIENT_ID>

Paso 5: introspección (backend a backend) ​

Endpoint: POST /oauth2/introspect

Uso recomendado:

  • Validar tokens cuando no se usa validación local JWT.
  • Obtener estado active y metadatos del token.

Paso 6: revocación y logout ​

  • Revocación de token: POST /oauth2/revoke
  • Logout del proveedor: /auth/logout/client

Recomendación de cierre de sesión:

  1. Invalidar sesión local en tu aplicación.
  2. Revocar token/refresh token cuando aplique.
  3. Redirigir a endpoint de logout del IdP.

Rutas absolutas ​

  • BASE_URL_IDP/auth/public/authorize/
  • BASE_URL_IDP/oauth2/token
  • BASE_URL_IDP/oidc/userinfo
  • BASE_URL_IDP/oauth2/introspect
  • BASE_URL_IDP/oauth2/revoke
  • BASE_URL_IDP/.well-known/jwks.json
  • BASE_URL_IDP/auth/logout/client

Flujo recomendado ​

  1. Implementar authorization_code con PKCE.
  2. Mantener secretos y refresh tokens fuera del frontend público.
  3. Validar state y nonce para mitigar replay/CSRF.
  4. Verificar firma y claims de tokens cuando aplique.
  5. Aplicar rotación y revocación de tokens en logout.

Tutorial rápido con oauth2c en Linux ​

oauth2c permite probar flujos OAuth2/OIDC desde terminal sin escribir una aplicación de prueba completa. En este módulo puede usarse para validar los dos grant types que expone el discovery:

  • authorization_code
  • client_credentials

Instalación en Linux ​

bash
go install github.com/cloudentity/oauth2c@latest

Variables de entorno sugeridas ​

bash
export BASE_URL_IDP='https://backend.idp-test.eje.gob.bo'
export CLIENT_ID='mi_cliente'
export CLIENT_SECRET='mi_secreto'

Flujo 1: authorization_code ​

Usa este flujo para probar login interactivo de usuario con navegador.

Requisitos previos:

  • El redirect_uri debe estar registrado para el cliente.
  • Para pruebas locales, una opción común es http://localhost:9876/callback.
bash
oauth2c "$BASE_URL_IDP" \
	--client-id "$CLIENT_ID" \
	--client-secret "$CLIENT_SECRET" \
	--response-types code \
	--response-mode query \
	--grant-type authorization_code \
	--auth-method client_secret_basic \
	--redirect-url http://localhost:9876/callback \
	--scopes openid,profile,email

Qué validar:

  1. Que se abra el navegador y el IdP complete el login.
  2. Que oauth2c reciba el callback y muestre el access_token.
  3. Que el token permita consultar GET /oidc/userinfo o las APIs que correspondan al scope solicitado.

Flujo 2: client_credentials ​

Usa este flujo para probar integración máquina a máquina sin usuario final.

bash
oauth2c "$BASE_URL_IDP" \
	--client-id "$CLIENT_ID" \
	--client-secret "$CLIENT_SECRET" \
	--grant-type client_credentials \
	--auth-method client_secret_basic \
	--scopes <SCOPES_AUTORIZADOS_PARA_ESE_CLIENTE>

Qué validar:

  1. Que el proveedor emita un access_token sin interacción de usuario.
  2. Que el token sea aceptado por el recurso para el cual fue emitido.
  3. Que los scopes del cliente coincidan con los permitidos por la configuración del IdP.

Recomendaciones prácticas ​

  • Empieza con authorization_code si quieres simular el recorrido de un usuario.
  • Usa client_credentials solo cuando estés validando un cliente de servicio.
  • Si el cliente no está registrado o el redirect_uri no coincide, el flujo fallará antes de obtener tokens.
  • Si necesitas un entorno distinto, cambia solo BASE_URL_IDP.

API REST de Directorio de Identidades ​

Fuente de contrato OpenAPI (schema): https://auth-backend-eje.app.com.bo/api/schema/

Base URL de integración para este capítulo: BASE_URL_IDP (definida en la Introducción del módulo).

Documentación API: BASE_URL_IDP/api/docs/

Prerrequisitos ​

Antes de consumir estos endpoints, el cliente debe:

  1. Autenticar usuario con OAuth2/OIDC.
  2. Obtener access_token válido.
  3. Enviar Authorization: Bearer <token> en cada request.
  4. Solicitar scopes suficientes para cada operación.

Esquema de seguridad del API:

  • BearerAuth (HTTP Bearer JWT).

Scopes oficiales para API de directorio:

  • directory:all:users:read
  • directory:external:users:read
  • directory:internal:users:read
  • directory:admin:users:read

Reglas de acceso por alcance:

  • directory:all:users:read: permite leer usuarios internos y externos.
  • directory:external:users:read: limita lectura a usuarios externos.
  • directory:internal:users:read: limita lectura a usuarios internos.
  • directory:admin:users:read: habilita visualizar datos de usuarios administradores de sistema.
TagMétodoRutaSummary
DirectoryGET/api/v1/directory/users/Listar identidades
DirectoryGET/api/v1/directory/users/{public_id}/Detalle de identidad

Endpoint de schema ​

El contrato OpenAPI se expone en:

  • GET /api/schema/

Parámetros opcionales soportados por el endpoint de schema:

  • format: json o yaml
  • lang: localización de etiquetas/descripciones del schema

Rutas absolutas ​

  • BASE_URL_IDP/api/v1/directory/users/
  • BASE_URL_IDP/api/v1/directory/users/{public_id}/

GET /api/v1/directory/users/ ​

Retorna el directorio de identidades registradas en el IdP. Cada item de resultado sigue un formato equivalente al payload de userinfo OIDC.

Query params ​

ParámetroTipoRequeridoDescripción
is_activebooleanNoFiltrar por estado activo.
is_eje_sysadminbooleanNoFiltrar por rol administrador. Requiere scope directory:admin:users:read.
pageintegerNoNúmero de página.
page_sizeintegerNoResultados por página (máximo 100).
terminostringNoBúsqueda por término clave.
tipo_usuariostringNoTipo de usuario: USUARIO_EXTERNO o USUARIO_INTERNO.

Respuesta 200 ​

application/json con estructura paginada:

json
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "sub": "abc123",
      "name": "Juan Perez",
      "given_name": "Juan",
      "family_name": "Perez",
      "preferred_username": "juan.perez",
      "email": "juan@example.com",
      "email_verified": true,
      "is_active": true,
      "tipo_usuario": "USUARIO_INTERNO",
      "nro_documento_identidad": "12345678",
      "nro_documento_identidad_verified": true,
      "fecha_nacimiento": "01/01/1990",
      "celular": "70000000"
    }
  ]
}

Respuestas de error ​

  • 400 Bad Request: parámetros inválidos.
  • 401 Unauthorized: token ausente, expirado o inválido.
  • 403 Forbidden: sin permisos/scopes suficientes.

Scopes requeridos ​

Se evalúan los scopes del token según el tipo de usuario consultado:

  • Lectura general de internos y externos: directory:all:users:read.
  • Lectura restringida a externos: directory:external:users:read.
  • Lectura restringida a internos: directory:internal:users:read.
  • Para ver datos de usuarios administradores de sistema: directory:admin:users:read.

GET /api/v1/directory/users/{public_id}/ ​

Retorna el detalle de una identidad por su public_id. La respuesta también es equivalente al formato de claims de userinfo OIDC.

Path params ​

ParámetroTipoRequeridoDescripción
public_idstringSíIdentificador público de la identidad.

Respuesta 200 ​

application/json:

json
{
  "sub": "abc123",
  "name": "Juan Perez",
  "given_name": "Juan",
  "family_name": "Perez",
  "preferred_username": "juan.perez",
  "email": "juan@example.com",
  "email_verified": true,
  "is_active": true,
  "nro_documento_identidad": "12345678",
  "nro_documento_identidad_verified": true
}

Respuestas de error ​

  • 401 Unauthorized: token ausente, expirado o inválido.
  • 403 Forbidden: token válido sin scope/permisos suficientes.
  • 404 Not Found: no existe identidad para ese public_id.

Scopes requeridos ​

Para consultar un public_id, el token debe contar con scope compatible con el tipo de usuario objetivo (all, internal o external).

Si la identidad objetivo corresponde a un usuario administrador de sistema, además se requiere directory:admin:users:read.

Esquemas de respuesta ​

DirectoryIdentity ​

Campos reportados por identidad (el backend puede incluir claims adicionales):

  • sub: string
  • name: string
  • given_name: string
  • family_name: string
  • preferred_username: string
  • email: string (email)
  • email_verified: boolean
  • is_active: boolean
  • tipo_usuario: string
  • nro_documento_identidad: string
  • nro_documento_identidad_verified: boolean
  • fecha_nacimiento: string
  • celular: string

PaginatedDirectoryIdentity ​

  • count: integer
  • next: string | null
  • previous: string | null
  • results: DirectoryIdentity[]

ApiErrorResponse ​

Formato estándar de error del directorio:

json
{
  "error_code": "insufficient_scope",
  "error_message": "El token no tiene permisos para acceder a este recurso.",
  "detail": "El token no tiene permisos para acceder a este recurso."
}

Campos:

  • error_code (requerido)
  • error_message (requerido)
  • detail (opcional)

Ejemplos de consumo ​

Listar identidades:

bash
curl 'BASE_URL_IDP/api/v1/directory/users/' \
	-H 'Authorization: Bearer ACCESS_TOKEN'

Listar con filtros y paginación:

bash
curl 'BASE_URL_IDP/api/v1/directory/users/?is_active=true&tipo_usuario=USUARIO_INTERNO&page=1&page_size=20&termino=juan' \
  -H 'Authorization: Bearer ACCESS_TOKEN'

Detalle de identidad:

bash
curl 'BASE_URL_IDP/api/v1/directory/users/8f9f9d10-xxxx-xxxx-xxxx-xxxxxxxxxxxx/' \
	-H 'Authorization: Bearer ACCESS_TOKEN'

Manejo de errores recomendado ​

  • 401 Unauthorized: token ausente, expirado o inválido.
  • 403 Forbidden: token válido pero sin scope/permisos suficientes.
  • 400 Bad Request: filtros o parámetros inválidos.
  • 404 Not Found: public_id inexistente.

Recomendaciones de seguridad ​

  • El token de acceso es de corta duración y debe obtenerse uno nuevo para cada petición
  • En caso de tokens obtenidos por interacción de usuario, usar refresh_token para renovar el token de acceso.
  • Separar clientes confidenciales (backend) de clientes públicos (frontend SPA o mobile) y usar flujos de autorización distintos para cada caso.

Consideraciones de integración ​

  • Para obtener listados, usar filtros y paginación según parámetros que exponga la implementación.
  • Para detalle, reemplazar {public_id} por el identificador público de la identidad.
  • Gestionar autorización con token OAuth2 válido y scopes acordes al caso de uso: directory:all:users:read, directory:external:users:read, directory:internal:users:read y directory:admin:users:read.
  • El filtro is_eje_sysadmin y el acceso a datos de administradores de sistema requieren directory:admin:users:read.
  • El campo results puede incluir claims OIDC adicionales definidos por el IdP.

Relación con estándares ​

  • Portador de token (Bearer) conforme a RFC 6750.
  • Scopes y autorización conforme a RFC 6749.
  • Claims de identidad alineados al modelo de userinfo de OpenID Connect.

Seguridad y Buenas Prácticas ​

Checklist de implementación ​

  • Usar siempre authorization_code + PKCE (S256) para login de usuarios.
  • Validar state en callback para prevenir CSRF.
  • Validar nonce cuando se utilice id_token.
  • No almacenar client_secret en frontend público.
  • Evitar guardar tokens en localStorage cuando se pueda usar sesión HttpOnly en backend/BFF.
  • Aplicar expiración corta para access_token y rotación de refresh_token.
  • Implementar revocación en logout (/oauth2/revoke) y cierre en IdP (/auth/logout/client).
  • Registrar auditoría de eventos de autenticación y errores de autorización.

Validaciones mínimas del token ​

Si validas tokens localmente:

  1. Verificar firma con jwks_uri.
  2. Verificar iss igual al issuer esperado.
  3. Verificar aud contiene tu client_id.
  4. Verificar vigencia (exp, iat) y tolerancia de reloj controlada.
  5. Verificar claims de contexto (acr, amr) si tu negocio lo exige.

Arquitectura recomendada ​

Para aplicaciones web:

  • Frontend redirige a login.
  • Backend/BFF gestiona intercambio de código por tokens.
  • Backend mantiene sesión segura y llama APIs de negocio.

Beneficio: reduce exposición de tokens y simplifica controles de seguridad.

Referencias de estándar ​

APIs de los backends ​

Esta sección describe las APIs que exponen los dos backends del sistema:

BackendSistemaAPI REST (/api/v1/)GraphQL
auth-backendSistema Único de Identificación (IDP)Directorio, aprobación de documentos y notificacionesadmin-schema, main-schema, public-schema
backend-seleccion-postulantesSistema de Procesos de Selección (SPS)Interoperabilidad (convocatorias, admitidos, postulaciones)admin-schema, comision-schema, postulante-schema, estacion-examen-schema

Tipos de API ​

  • REST (/api/v1/): APIs de interoperabilidad para sistemas externos. Están versionadas, protegidas con tokens OAuth2 emitidos por el IDP y documentadas en OpenAPI.
  • GraphQL: APIs internas consumidas por las interfaces del propio sistema. No están pensadas para integraciones externas y pueden cambiar sin aviso.

Ruta sugerida de lectura ​

  1. Introducción: URLs base, autenticación y documentación OpenAPI.
  2. API REST del IDP.
  3. API REST del SPS.
  4. APIs GraphQL internas.

TIP

Para obtener tokens (flujos authorization_code + PKCE y client_credentials) consulte la Guía de Integración OAuth2 y OpenID Connect.

Introducción ​

URLs base por entorno ​

VariableTestProducción
BASE_URL_IDPhttps://backend.idp-test.eje.gob.bohttps://backend.idp.eje.gob.bo
BASE_URL_SPShttps://backend.sps-test.eje.gob.bohttps://backend.sps.eje.gob.bo

En las páginas siguientes, reemplace BASE_URL_IDP o BASE_URL_SPS por la URL del entorno objetivo.

Documentación OpenAPI ​

Ambos backends generan su especificación OpenAPI con drf-spectacular y la publican en las mismas rutas:

RutaContenido
/api/schema/Especificación OpenAPI (YAML; JSON con ?format=json)
/api/docs/Swagger UI
/api/redoc/ReDoc

Ejemplo: BASE_URL_SPS/api/docs/.

La especificación OpenAPI es la referencia completa de cada endpoint (campos de request y response, códigos de estado y ejemplos). Esta sección resume los endpoints disponibles y los requisitos para consumirlos.

Autenticación ​

Todas las APIs REST de /api/v1/ requieren un access token emitido por el IDP, enviado en la cabecera:

http
Authorization: Bearer <access_token>
  • La audiencia del token es eje-idp-api.
  • El cliente OAuth2 debe tener asignados los scopes que exige cada endpoint y solicitarlos al pedir el token.
  • Para integraciones sistema a sistema se usa client_credentials; para acciones en nombre de un usuario, authorization_code + PKCE.

Cómo valida el token cada backend:

BackendValidación
IDPContra su base de tokens emitidos: verifica que exista, que no esté revocado ni expirado y que tenga la audiencia esperada.
SPSLocalmente como JWT: firma con las claves del jwks_uri del IDP, issuer del discovery, audiencia eje-idp-api, claims obligatorios exp, iss, aud, sub y token_use=access_token. Los scopes se leen del claim scope.

INFO

Como el SPS valida el JWT localmente, un token revocado en el IDP sigue siendo aceptado por el SPS hasta que expira.

Respuestas de error ​

CódigoSignificado
400Parámetros o cuerpo inválidos
401Token ausente, inválido o expirado
403El token no tiene el scope requerido
404Recurso no encontrado (o no visible para el cliente)

El formato del cuerpo de error difiere entre backends; se detalla en la página de cada uno.

Raíz de la API ​

Cada grupo de recursos expone en su prefijo una vista raíz (generada por el DefaultRouter de Django REST Framework) que lista los recursos disponibles. Por ejemplo, GET BASE_URL_SPS/api/v1/.

API REST del IDP ​

Backend: auth-backend. URL base: BASE_URL_IDP/api/v1/.

Convenciones ​

  • Autenticación: Authorization: Bearer <access_token> (ver Introducción).

  • Aislamiento por cliente: en aprobación de documentos y notificaciones, cada cliente OAuth2 solo ve los recursos que él mismo registró; el cliente se asigna automáticamente al crear el recurso.

  • Identificador: los recursos se consultan por su codigo.

  • Paginación: por número de página.

    ParámetroDefaultMáximo
    page1—
    page_size10100
  • Formato de error:

    json
    {
      "error_code": "...",
      "error_message": "...",
      "detail": "..."
    }

Directorio de identidades ​

MétodoRutaDescripción
GET/api/v1/directory/users/Lista identidades del directorio
GET/api/v1/directory/users/{public_id}/Obtiene una identidad por public_id

Scopes: directory:all:users:read, directory:external:users:read, directory:internal:users:read y directory:admin:users:read, según el tipo de usuarios a consultar.

La documentación detallada (parámetros, respuestas y ejemplos) está en API REST de Directorio de Identidades.

Aprobación de documentos ​

Prefijo: /api/v1/aprobacion-documentos/.

MétodoRutaDescripciónScope
GETdocumentos-oficiales/Listar documentos oficialesapprovaldocs:officialdocuments:read
POSTdocumentos-oficiales/Registrar documento oficialapprovaldocs:officialdocuments:create
GETdocumentos-oficiales/{codigo}/Obtener documento oficialapprovaldocs:officialdocuments:read
GETsolicitudes-aprobacion/Listar solicitudes de aprobaciónapprovaldocs:approvalrequests:read
POSTsolicitudes-aprobacion/Registrar solicitud de aprobaciónapprovaldocs:approvalrequests:create
GETsolicitudes-aprobacion/{codigo}/Obtener solicitud de aprobaciónapprovaldocs:approvalrequests:read

El registro de documentos oficiales recibe el archivo como multipart/form-data.

Notificaciones ​

Prefijo: /api/v1/notificaciones/.

MétodoRutaDescripciónScope
GETsolicitudes-notificacion/Listar solicitudes de notificaciónnotifications:requests:read
POSTsolicitudes-notificacion/Registrar solicitud de notificaciónnotifications:requests:create
GETsolicitudes-notificacion/{codigo}/Obtener solicitud de notificaciónnotifications:requests:read
POSTsolicitudes-notificacion/{codigo}/reemplazar-formulario/Reemplazar el formulario de notificaciónnotifications:requests:create

Reemplazar formulario ​

Reemplaza la solicitud de aprobación del formulario de notificación sin crear una nueva solicitud de notificación:

  • Se permite en cualquier estado (pendiente, en proceso, cancelada o fallida), salvo si la solicitud ya fue completada.
  • Si no se adjunta un nuevo formulario_notificacion, se reutiliza el documento oficial anterior con una nueva solicitud de aprobación.
  • Si la solicitud original no tenía formulario (no notificaba por Ciudadanía Digital), el endpoint agrega ese canal: formulario_notificacion pasa a ser obligatorio y se crea un envío en espera por cada destinatario ya registrado.

Referencia completa ​

Campos de cada request/response: BASE_URL_IDP/api/docs/.

API REST del SPS ​

Backend: backend-seleccion-postulantes (app interoperabilidad). URL base: BASE_URL_SPS/api/v1/.

API de solo lectura para que sistemas externos consulten información de los procesos de selección. No expone operaciones de creación, modificación ni eliminación.

Convenciones ​

  • Autenticación: Authorization: Bearer <access_token> emitido por el IDP (ver Introducción). Se aceptan tokens obtenidos con client_credentials o authorization_code + PKCE.

  • Alcance de los datos: solo se exponen convocatorias con resultados publicados.

  • Paginación: por desplazamiento (limit / offset).

    RecursoDefault limitMáximo
    General20100
    postulaciones1050
    historial-postulacionesSin paginación—
  • Formato de error (Django REST Framework):

    json
    { "detail": "..." }

    Para parámetros requeridos faltantes: { "<parametro>": "Parámetro requerido." }.

Endpoints ​

MétodoRutaDescripciónParámetrosScope
GETconvocatorias/Lista convocatorias con resultados publicadoslimit, offsetseleccion:convocatorias:read
GETconvocatorias/{codigo}/Obtiene una convocatoriacodigo (path)seleccion:convocatorias:read
GETofertas-departamentales/Plazas ofertadas por departamentoconvocatoria (requerido), limit, offsetseleccion:convocatorias:read
GETpostulantes-admitidos/Postulantes admitidos de una convocatoriaconvocatoria (requerido), limit, offsetseleccion:admitidos:read
GETpostulaciones/Historial de postulaciones de una personausuario_base (requerido, idp_sub), limit, offsetseleccion:postulaciones:read
GETusuarios-base/{idp_sub}/Datos de identidad y contacto de un postulanteidp_sub (path)seleccion:postulaciones:read
GEThistorial-postulaciones/Búsqueda en postulaciones actuales e históricastermino (requerido)seleccion:historialpostulaciones:read

Notas:

  • convocatorias/ es el punto de entrada: de allí se obtienen los codigo que usan los demás recursos. El codigo puede contener /.
  • historial-postulaciones/: termino acepta un número de documento o un nombre de al menos 3 caracteres.
  • usuarios-base/ no tiene listado; solo consulta por idp_sub (el sub del usuario en el IDP). Es el identificador para correlacionar a una persona entre sistemas.
  • postulantes-admitidos/ y postulaciones/ incluyen el usuario anidado con su idp_sub, email y celular.

Flujo típico de consulta ​

Referencia completa ​

Campos de cada respuesta: BASE_URL_SPS/api/docs/.

APIs GraphQL internas ​

Las interfaces web del sistema se comunican con los backends mediante GraphQL. Estas APIs son internas: su contrato sigue la evolución de los frontends y no se garantiza compatibilidad para integraciones externas. Para integraciones use las APIs REST.

Características comunes ​

  • Vista: FileUploadGraphQLView (graphene-file-upload). Acepta peticiones JSON estándar y multipart/form-data para subir archivos según la especificación GraphQL multipart request.
  • Implementación: graphene-django + django-graphbox. Cada schema se arma combinando las queries y mutations de varias apps de Django.
  • Método: POST al path del schema (por ejemplo BASE_URL_SPS/admin-schema/).
  • Cliente en los frontends: Apollo Client. La URL se arma como VITE_API_ENDPOINT + VITE_API_ENDPOINT_PATH (ver Imágenes Docker).
  • GraphiQL: disponible en el mismo path solo si el backend corre con DEBUG=True.

Autenticación y sesión ​

Los schemas GraphQL no usan tokens OAuth2 en la cabecera: trabajan con una sesión por cookie que el backend crea al completar el inicio de sesión.

  • CSRF: las peticiones POST requieren la cabecera X-CSRFToken. El token se obtiene en:
    • IDP: BASE_URL_IDP/auth/public/csrf-token/
    • SPS: BASE_URL_SPS/iam-core/csrf-token/
  • Sesión: el backend guarda el identificador de sesión hasheado en base de datos y lo valida en cada petición.
  • Autorización por portal: en el SPS cada schema tiene su gestor de sesión (admin_session_manager, comision_session_manager, postulante_session_manager, estacion_examen_session_manager), que verifica que el usuario esté activo y tenga acceso vigente a ese portal (y al grupo o rol que exige la operación).
  • Modo standby: con SYSTEM_STATUS=False, StandbyMiddleware responde 503 ({"error": "STANDBY", ...}) a toda petición excepto /health/, en ambos backends.

Schemas del IDP ​

Backend: auth-backend. URL base: BASE_URL_IDP.

PathFrontendAudienciaApps incluidas
/admin-schema/auth-frontend-adminAdministradores del IDPiam_core, oauth2_eje, oidc, aprobacion_documentos, notificaciones
/main-schema/auth-authorization-frontendRegistro de cuentas en el portal de autorizacióniam_core
/public-schema/frontend-public-authUsuarios gestionando su cuentaiam_core, aprobacion_documentos

Schemas del SPS ​

Backend: backend-seleccion-postulantes. URL base: BASE_URL_SPS.

PathFrontendAudiencia
/admin-schema/frontend-portal-admin-seleccion-postulantesPersonal administrativo
/comision-schema/frontend-portal-comisiones-seleccion-postulantesMiembros de comisiones
/postulante-schema/frontend-portal-postulantes-seleccion-postulantesPostulantes
/estacion-examen-schema/frontend-estacion-examenEstaciones de examen en línea

Apps incluidas en cada schema:

SchemaApps
admincatalogos, iam_core, document_engine, analitica_planillas, sgc, acciones_previas, custodio_integration, convocatorias, difusion, comisiones, resoluciones, trabajo_comisiones, conclusion_proceso, impugnaciones, reportes, interoperabilidad, datos_historicos, notificaciones, consultas, seeders
comisioniam_core, document_engine, sgc, convocatorias, postulaciones, comisiones, trabajo_comisiones, impugnaciones, interoperabilidad, reportes
postulantecatalogos, iam_core, document_engine, sgc, convocatorias, difusion, postulaciones, trabajo_comisiones, impugnaciones, consultas
estacion_examentrabajo_comisiones

Estaciones de examen ​

Las estaciones no inician sesión con usuario: se registran subiendo un archivo de credencial JWT generado desde el sistema para el recinto. Estos endpoints REST están exentos de CSRF.

MétodoRutaDescripción
POST/trabajo-comisiones/registrar-estacion-examen/Recibe el archivo credential_jwt (multipart), lo valida y crea la cookie de sesión de la estación
POST/trabajo-comisiones/sustituir-estacion-examen/Sustituye una estación registrada usando una credencial JWT

Con esa sesión, la estación opera contra /estacion-examen-schema/.

Actualización automática de estados ​

El SPS incluye CierreFaseAutomaticoMiddleware: antes de procesar cualquier petición a los cuatro schemas GraphQL, cierra las fases de convocatoria vencidas y marca como finalizadas las rendiciones de examen cuyo tiempo terminó. Por eso los estados pueden cambiar sin una acción explícita del usuario.

Explorar un schema ​

Con DEBUG=True (entorno local), abra el path del schema en el navegador (por ejemplo http://localhost:8000/postulante-schema/) para usar GraphiQL, que incluye el explorador de documentación del schema.

Para auditar qué campos de un schema usa su frontend, vea el script auditar_types_graphql en Scripts útiles de los backends.

Infraestructura y despliegue ​

Consulte la documentación de infraestructura y despliegue para obtener información sobre cómo configurar y desplegar los servicios y aplicaciones de la Escuela de Jueces del Estado.

Entorno Testing ​

Sistema Único de Identificación de la Escuela de Jueces del Estado (IDP)

(*.idp-test.eje.gob.bo)

RepoNombreDescripciónSubdominioPuerto
auth-backendIDP BackendAPI OAuth2/OIDC, gestión de identidad y autenticaciónbackend.idp-test.eje.gob.bo8500
auth-authorization-frontendPortal de AutorizaciónPantalla de login y consentimiento OIDC para todos los sistemasauth.idp-test.eje.gob.bo8300
auth-frontend-adminAdmin IDPGestión administrativa de usuarios, clientes OAuth2 y permisosadmin.idp-test.eje.gob.bo8301
frontend-public-authPortal de CuentaAutogestión de cuenta para cada usuario (perfil, contraseña, 2FA)cuenta.idp-test.eje.gob.bo8302

Sistema de Procesos de Selección (SPS)

(*.sps-test.eje.gob.bo)

RepoNombreDescripciónSubdominioPuerto
backend-seleccion-postulantesSeleccionador BackendAPIs del proceso de selección de postulantesbackend.sps-test.eje.gob.bo8501
frontend-portal-admin-seleccion-postulantesAdmin SeleccionadorGestión administrativa del proceso de selección y convocatoriasadmin.sps-test.eje.gob.bo8303
frontend-portal-comisiones-seleccion-postulantesPortal de ComisionesPortal de acceso para las comisionescomisiones.sps-test.eje.gob.bo8304
frontend-portal-postulantes-seleccion-postulantesPortal de PostulantesPortal público para que los postulantes apliquen a convocatoriaspostulantes.sps-test.eje.gob.bo8305
frontend-estacion-examenEstación de ExamenEstación de rendición de exámenes presenciales en líneaestacion.sps-test.eje.gob.bo8306

Documentación

(*.docs-test.eje.gob.bo)

RepoNombreDescripciónSubdominioPuerto
frontend-manual-tecnicoManual TécnicoDocumentación técnica para desarrolladores de ambos sistemastecnico.docs-test.eje.gob.bo8401
frontend-manual-de-usuarioManual de UsuarioGuía de uso para usuarios finales de ambos sistemasusuario.docs-test.eje.gob.bo8402

Entorno Producción ​

Sistema Único de Identificación de la Escuela de Jueces del Estado (IDP)

(*.idp.eje.gob.bo)

RepoNombreDescripciónSubdominioPuerto
auth-backendIDP BackendAPI OAuth2/OIDC, gestión de identidad y autenticaciónbackend.idp.eje.gob.bo8500
auth-authorization-frontendPortal de AutorizaciónPantalla de login y consentimiento OIDC para todos los sistemasauth.idp.eje.gob.bo8300
auth-frontend-adminAdmin IDPGestión administrativa de usuarios, clientes OAuth2 y permisosadmin.idp.eje.gob.bo8301
frontend-public-authPortal de CuentaAutogestión de cuenta para cada usuario (perfil, contraseña, 2FA)cuenta.idp.eje.gob.bo8302

Sistema de Procesos de Selección (SPS)

(*.sps.eje.gob.bo)

RepoNombreDescripciónSubdominioPuerto
backend-seleccion-postulantesSeleccionador BackendAPIs del proceso de selección de postulantesbackend.sps.eje.gob.bo8501
frontend-portal-admin-seleccion-postulantesAdmin SeleccionadorGestión administrativa del proceso de selección y convocatoriasadmin.sps.eje.gob.bo8303
frontend-portal-comisiones-seleccion-postulantesPortal de ComisionesPortal de acceso para las comisionescomisiones.sps.eje.gob.bo8304
frontend-portal-postulantes-seleccion-postulantesPortal de PostulantesPortal público para que los postulantes apliquen a convocatoriaspostulantes.sps.eje.gob.bo8305
frontend-estacion-examenEstación de ExamenEstación de rendición de exámenes presenciales en líneaestacion.sps.eje.gob.bo8306

Documentación

(*.docs.eje.gob.bo)

RepoNombreDescripciónSubdominioPuerto
frontend-manual-tecnicoManual TécnicoDocumentación técnica para desarrolladores de ambos sistemastecnico.docs.eje.gob.bo8401
frontend-manual-de-usuarioManual de UsuarioGuía de uso para usuarios finales de ambos sistemasusuario.docs.eje.gob.bo8402

Arquitectura de despliegue ​

Visión general ​

Todos los servicios web se ejecutan como contenedores Docker en un host por entorno (test y producción). El despliegue está centralizado en el repositorio infra-deploy, que actúa como orquestador: contiene los docker-compose.yml de cada sistema y el pipeline que los aplica en el host vía SSH.

EntornoRama GitTag de imagenVariables CI/CD
Testdevelopdevelop-latestDEV_*
Producciónmainmain-latestPROD_*

Repositorio infra-deploy ​

infra-deploy/
├── .gitlab-ci.yml          # Pipeline orquestador (job deploy-orquestador)
├── idp/docker-compose.yml  # Stack del IDP
├── seleccionador/docker-compose.yml  # Stack del SPS
└── docs/docker-compose.yml # Stack de documentación

Cada docker-compose.yml define un proyecto Compose independiente (name:), con su propia red y sus propios servicios.

Stacks ​

IDP (name: idp) ​

Red: idp-net (bridge).

ServicioPuerto hostVolúmenesenv_file
auth-backend8500Síback.env
auth-authorization-frontend8300No—
auth-frontend-admin8301No—
frontend-public-auth8302No—

Seleccionador (name: seleccionador) ​

Red: seleccionador-net (bridge).

ServicioPuerto hostVolúmenesenv_file
backend-seleccion-postulantes8501Síback.env
frontend-portal-admin-seleccion-postulantes8303No—
frontend-portal-comisiones-seleccion-postulantes8304No—
frontend-portal-postulantes-seleccion-postulantes8305No—
frontend-estacion-examen8306No—

Documentación (name: docs) ​

Red: docs-net (bridge).

ServicioPuerto host
frontend-manual-tecnico8401
frontend-manual-de-usuario8402

INFO

Todos los contenedores escuchan en el puerto 80 interno. Los subdominios públicos de cada servicio se detallan en Mapas de subdominios y puertos.

Configuración común de los servicios ​

Todos los servicios comparten la misma configuración base en Compose:

yaml
image: "git.eje.gob.bo:5050/proceso-seleccion/<servicio>:${IMAGE_TAG}"
container_name: <servicio>
restart: unless-stopped
healthcheck:
  test: ["CMD", "curl", "-fsS", "http://localhost/health/"]
  interval: 30s
  timeout: 5s        # backends | 3s frontends
  retries: 3
  start_period: 20s  # backends | 5s frontends
logging:
  driver: json-file
  options:
    max-size: "10m"
    max-file: "5"
  • IMAGE_TAG se lee del archivo .env del stack, que el pipeline genera en cada despliegue (develop-latest o main-latest).
  • restart: unless-stopped reinicia el contenedor ante caídas, salvo que se haya detenido manualmente.
  • Los logs rotan a 10 MB con un máximo de 5 archivos por contenedor.

Backends ​

Los backends montan adicionalmente:

yaml
env_file:
  - back.env
volumes:
  - ./storage/public:/app/public_files
  - ./storage/private:/app/private_files
  - ./back.env:/app/.env:ro
Ruta en hostRuta en contenedorUso
storage/public//app/public_filesArchivos públicos, servidos por nginx en /files/
storage/private//app/private_filesArchivos privados, solo accesibles vía X-Accel-Redirect
back.env/app/.env (ro)Variables de entorno de Django

Estructura en el host ​

El pipeline crea y mantiene esta estructura bajo DEPLOY_BASE_PATH (variable distinta por entorno):

${DEPLOY_BASE_PATH}/
├── idp/
│   ├── docker-compose.yml   # copiado desde infra-deploy/idp
│   ├── .env                 # IMAGE_TAG=<develop|main>-latest
│   ├── back.env             # variables + secretos de auth-backend
│   └── storage/
│       ├── public/
│       └── private/
├── seleccionador/
│   ├── docker-compose.yml
│   ├── .env
│   ├── back.env             # variables + secretos de backend-seleccion-postulantes
│   └── storage/
│       ├── public/
│       └── private/
└── docs/
    ├── docker-compose.yml
    └── .env

WARNING

docker-compose.yml, .env y back.env se sobrescriben en cada despliegue. Cualquier cambio manual en el host se pierde; los cambios deben hacerse en infra-deploy o en las variables CI/CD de GitLab.

Custodio ​

custodio no se despliega en el host: es una aplicación de escritorio (Python + Tkinter) que valida, consolida y cifra los bancos de preguntas para la comisión de elaboración. Su pipeline genera ejecutables para Linux y Windows y los publica en el Package Registry de GitLab. Ver Pipelines CI/CD.

Imágenes Docker ​

Todas las imágenes usan build multi-etapa: una etapa de construcción con las herramientas de compilación y una etapa final mínima con solo lo necesario para ejecutar.

Las imágenes se publican en el registry de GitLab:

git.eje.gob.bo:5050/proceso-seleccion/<repositorio>:<tag>

Backends (Django) ​

Aplica a auth-backend y backend-seleccion-postulantes.

Etapas ​

EtapaBaseContenido
builderpython:3.11-slimInstala requirements.prod.txt en /install (pip --prefix)
runtimepython:3.11-slimCopia /install a /usr/local, el código fuente, nginx y entrypoint.sh

Paquetes de sistema en runtime:

  • auth-backend: nginx, curl.
  • backend-seleccion-postulantes: nginx, curl, ghostscript y librerías nativas que WeasyPrint y OpenCV cargan en tiempo de ejecución (libpango, libpangocairo, libgdk-pixbuf, libcairo2, libffi8, libglib2.0-0, libgl1, libsm6, libxext6, libxrender1, shared-mime-info, fuentes liberation y dejavu). La etapa builder instala además las librerías de desarrollo necesarias para compilar estas dependencias.

Usuario y permisos ​

El contenedor corre como appuser (no root). nginx (www-data) y gunicorn (appuser) comparten acceso a los volúmenes:

dockerfile
RUN useradd -m appuser \
    && mkdir -p /app/public_files /app/private_files \
    && chown -R appuser:www-data /app \
    && chmod -R 775 /app/public_files /app/private_files

TIP

Si en el host los directorios storage/public o storage/private tienen otro propietario, el backend no podrá escribir archivos. Deben ser escribibles por el UID de appuser dentro del contenedor.

Procesos dentro del contenedor ​

Cada contenedor de backend ejecuta nginx + gunicorn:

  • gunicorn: 2 workers, timeout 60 s, logs de acceso y error a stdout/stderr.
  • nginx: proxy_read_timeout 60s y cabeceras Host, X-Real-IP, X-Forwarded-For, X-Forwarded-Proto hacia gunicorn.
  • La ruta /private_files/ es internal: solo se sirve cuando Django responde con la cabecera X-Accel-Redirect tras validar el acceso.

Health check ​

dockerfile
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
    CMD curl -fsS http://localhost/health/ || exit 1

El endpoint /health/ está definido en Django (auth_interop_backend/urls.py y seleccion_backend/urls.py).

entrypoint.sh ​

Se ejecuta en cada arranque del contenedor.

auth-backend

sh
python manage.py migrate --noinput
python manage.py collectstatic --noinput --clear -v 0
python manage.py rotate_signing_keys
python manage.py create_default_scopes
python manage.py create_claim_definitions
nginx -g "daemon off;" &
exec gunicorn auth_interop_backend.wsgi:application --bind 127.0.0.1:8000 ...

backend-seleccion-postulantes

sh
python manage.py migrate --noinput
python manage.py collectstatic --noinput --clear -v 0
# Bucle en segundo plano: cada 30 min
python manage.py limpiar_segmentos_huerfanos
nginx -g "daemon off;" &
exec gunicorn seleccion_backend.wsgi:application --bind 127.0.0.1:8000 ...

INFO

Las migraciones se aplican automáticamente al desplegar. Los comandos de arranque se describen en Scripts útiles de los backends.

Frontends (Vue + Vite) ​

Aplica a los portales del IDP, del SPS y a la estación de examen.

EtapaBaseContenido
buildernode:24-alpinepnpm@11.3.0 vía corepack, pnpm install --frozen-lockfile, pnpm run build
runtimenginx:alpineCopia /app/dist a /usr/share/nginx/html y nginx.conf

Variables de build (VITE_*) ​

Las variables VITE_* se incrustan en el bundle durante el build (se pasan como --build-arg). No pueden cambiarse en runtime: cualquier cambio requiere un nuevo build.

RepositorioBuild args
auth-authorization-frontendVITE_TITLE_APP, VITE_API_ENDPOINT, VITE_API_ENDPOINT_FILES, VITE_PUBLIC_AUTH_PORTAL, VITE_ADMIN_AUTH_PORTAL
auth-frontend-adminVITE_TITLE_APP, VITE_API_ENDPOINT, VITE_API_ENDPOINT_PATH, VITE_API_ENDPOINT_FILES, VITE_LOGIN_REDIRECT_URL, VITE_MANUAL_TECNICO_URL, VITE_MANUAL_USUARIO_URL
frontend-public-authVITE_TITLE_APP, VITE_API_ENDPOINT, VITE_API_ENDPOINT_PATH, VITE_API_ENDPOINT_FILES, VITE_API_FILES_PRIVATE, VITE_LOGIN_REDIRECT_URL, VITE_JACOBITUS_API_URL
frontend-portal-admin-seleccion-postulantesVITE_TITLE_APP, VITE_API_ENDPOINT, VITE_API_ENDPOINT_PATH, VITE_API_ENDPOINT_FILES, VITE_AUTH_ORIGIN_PORTAL, VITE_API_FILES_PRIVATE, VITE_MANUAL_USUARIO_URL, VITE_MANUAL_TECNICO_URL, VITE_AUTH_PUBLIC_URL
frontend-portal-comisiones-seleccion-postulantesVITE_TITLE_APP, VITE_API_ENDPOINT, VITE_API_ENDPOINT_PATH, VITE_API_ENDPOINT_FILES, VITE_API_FILES_PRIVATE, VITE_AUTH_ORIGIN_PORTAL, VITE_AUTH_PUBLIC_URL
frontend-portal-postulantes-seleccion-postulantesVITE_TITLE_APP, VITE_API_ENDPOINT, VITE_API_ENDPOINT_PATH, VITE_API_ENDPOINT_FILES, VITE_API_FILES_PRIVATE, VITE_LOGIN_REDIRECT_URL, VITE_AUTH_ORIGIN_PORTAL, VITE_DOMAIN_PUBLIC_URL
frontend-estacion-examenVITE_TITLE_APP, VITE_API_ENDPOINT, VITE_API_ENDPOINT_PATH, VITE_API_FILES_PRIVATE

Los valores se toman de las variables CI/CD DEV_VITE_* o PROD_VITE_* según la rama. Ver Variables y secretos de CI/CD.

nginx de los frontends ​

nginx
server {
   listen 80;
   root /usr/share/nginx/html;
   index index.html;

   gzip on;
   gzip_types text/plain text/css application/javascript application/json image/svg+xml;

   location /health {
      access_log off;
      default_type text/plain;
      return 200 'OK';
   }

   # SPA: rutas del router resuelven a index.html
   location / {
      try_files $uri $uri/ /index.html;
   }

   location ~* \.(js|css|png|jpg|svg|ico|woff2)$ {
      expires 1y;
      add_header Cache-Control "public, immutable";
   }
}

Manuales (VitePress) ​

Aplica a frontend-manual-tecnico y frontend-manual-de-usuario.

Misma estructura que los frontends, con estas diferencias:

  • El build ejecuta pnpm run docs:build.
  • Se copia /app/.vitepress/dist en lugar de /app/dist.
  • No reciben build args.
  • Usan la misma configuración de nginx que los frontends.

Construcción local ​

Para validar una imagen antes de subir cambios:

sh
# Backend
docker build -t auth-backend:local ./auth-backend

# Frontend (los build args son obligatorios para un bundle funcional)
docker build \
  --build-arg VITE_TITLE_APP="..." \
  --build-arg VITE_API_ENDPOINT="..." \
  -t frontend-portal-admin:local ./frontend-portal-admin-seleccion-postulantes

Pipelines CI/CD ​

El despliegue se realiza en dos niveles de pipelines de GitLab:

  1. Pipeline de cada repositorio de servicio: construye la imagen Docker, la publica en el registry y dispara el orquestador.
  2. Pipeline del orquestador (infra-deploy): se conecta por SSH al host del entorno y actualiza los contenedores.
RamaEntornoTag latestVariables usadas
developTestdevelop-latestDEV_*
mainProducciónmain-latestPROD_*

Todos los jobs se ejecutan en runners con el tag test (excepto el build de Windows de Custodio, que usa el tag windows).

Pipeline de los repositorios de servicio ​

Aplica a todos los backends, frontends y manuales.

Stages ​

StageJobImagen del job
deploybuilddocker:26 + servicio docker:26-dind
triggertrigger-infra-deploycurlimages/curl:8.8.0

Job build ​

Se ejecuta en develop y main:

yaml
build:
  stage: deploy
  image: docker:26
  services:
    - docker:26-dind
  rules:
    - if: '$CI_COMMIT_BRANCH == "develop"'
    - if: '$CI_COMMIT_BRANCH == "main"'
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
  script:
    - export TAG="${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}"
    - docker build -t "$IMAGE:$TAG" -t "$IMAGE:${CI_COMMIT_REF_SLUG}-latest" .
    - docker push "$IMAGE:$TAG"
    - docker push "$IMAGE:${CI_COMMIT_REF_SLUG}-latest"

Cada build publica dos tags:

TagEjemploUso
<rama>-<sha corto>main-a1b2c3d4Versión inmutable; permite rollback
<rama>-latestmain-latestTag que usa docker-compose.yml en el host

Frontends: variables por rama ​

En los frontends, cada regla asigna las variables VITE_* desde su versión DEV_ o PROD_ y se pasan como --build-arg:

yaml
rules:
  - if: '$CI_COMMIT_BRANCH == "develop"'
    variables:
      VITE_TITLE_APP: $DEV_VITE_TITLE_APP
      VITE_API_ENDPOINT: $DEV_VITE_API_ENDPOINT
      # ...
  - if: '$CI_COMMIT_BRANCH == "main"'
    variables:
      VITE_TITLE_APP: $PROD_VITE_TITLE_APP
      VITE_API_ENDPOINT: $PROD_VITE_API_ENDPOINT
      # ...
script:
  - |
    docker build \
      --build-arg VITE_TITLE_APP="${VITE_TITLE_APP}" \
      --build-arg VITE_API_ENDPOINT="${VITE_API_ENDPOINT}" \
      # ...
      -t "$IMAGE:$TAG" -t "$IMAGE:${CI_COMMIT_REF_SLUG}-latest" .

La lista de build args de cada frontend está en Imágenes Docker.

Job trigger-infra-deploy ​

Se ejecuta después de build (needs: build) y dispara el pipeline de infra-deploy en la misma rama, indicando qué servicio desplegar:

yaml
trigger-infra-deploy:
  stage: trigger
  image: curlimages/curl:8.8.0
  rules:
    - if: '$CI_COMMIT_BRANCH == "develop"'
    - if: '$CI_COMMIT_BRANCH == "main"'
  needs:
    - job: build
  script:
    - |
      curl -fsSk --retry 3 -X POST \
        --form token=$INFRA_DEPLOY_TRIGGER_TOKEN \
        --form ref=$CI_COMMIT_BRANCH \
        --form "variables[SERVICE_NAME]=<nombre-del-servicio>" \
        "https://git.eje.gob.bo/api/v4/projects/13/trigger/pipeline"
  • ref determina el entorno: develop → test, main → producción.
  • SERVICE_NAME debe coincidir con el nombre del servicio en el docker-compose.yml (igual al nombre del repositorio).

Pipeline orquestador (infra-deploy) ​

Un único job, deploy-orquestador (stage deploy, imagen alpine:3.19), que se ejecuta en develop y main. Según la rama, mapea las variables DEV_* o PROD_* a nombres genéricos:

yaml
rules:
  - if: '$CI_COMMIT_BRANCH == "develop"'
    variables:
      DEPLOY_USER: $DEV_DEPLOY_USER
      DEPLOY_HOST: $DEV_DEPLOY_HOST
      SSH_PRIVATE_KEY: $DEV_SSH_PRIVATE_KEY
      IMAGE_TAG: develop-latest
      IDP_DEPLOY_PATH: $DEV_DEPLOY_BASE_PATH/idp
      SELECCIONADOR_DEPLOY_PATH: $DEV_DEPLOY_BASE_PATH/seleccionador
      DOCS_DEPLOY_PATH: $DEV_DEPLOY_BASE_PATH/docs
      # ... BACK_ENV y secretos de cada backend
  - if: '$CI_COMMIT_BRANCH == "main"'
    variables:
      # ... mismas variables con prefijo PROD_ e IMAGE_TAG: main-latest

Pasos del despliegue ​

  1. Conexión SSH: instala openssh-client, carga la clave en ssh-agent y registra el host en known_hosts con ssh-keyscan.

  2. Directorios: crea en el host storage/public y storage/private de IDP y Seleccionador, y el directorio de docs.

  3. Archivos de configuración: copia por scp:

    • .env con IMAGE_TAG=<rama>-latest a los tres stacks.
    • idp/, seleccionador/ y docs/docker-compose.yml.
    • El archivo de variables de cada backend como back.env.tmp.
  4. Composición de back.env (en el host): por cada backend concatena back.env.tmp con los secretos individuales y elimina el temporal. Se regenera completo en cada ejecución:

    sh
    cat back.env.tmp > back.env
    echo "SECRET_KEY=${IDP_SECRET_KEY}" >> back.env
    echo "SIGNING_KEY_MASTER_SECRET=${IDP_SIGNING_KEY_MASTER_SECRET}" >> back.env
    echo "DATABASE_PASS=${IDP_DATABASE_PASS}" >> back.env
    rm back.env.tmp
  5. Login al registry con el deploy token del grupo (REGISTRY_DEPLOY_TOKEN_USER / REGISTRY_DEPLOY_TOKEN_PASSWORD).

  6. Despliegue:

    • Con SERVICE_NAME (disparado por un repositorio): ubica el stack al que pertenece el servicio y ejecuta solo para él:

      sh
      docker compose pull "$TARGET_SERVICE"
      docker compose up -d --no-deps "$TARGET_SERVICE"

      Si el valor no es un servicio conocido, el job falla.

    • Sin SERVICE_NAME (ejecución manual del pipeline): hace pull y up -d --no-deps de todos los servicios de los tres stacks.

  7. Verificación: espera hasta 120 s (compartidos entre los tres stacks) a que todos los contenedores estén running y, si tienen health check, healthy. Si un backend no terminó de iniciar, imprime sus últimas 200 líneas de log.

  8. Resumen: imprime una tabla con proyecto, servicio, estado, salud y puertos publicados.

TIP

El paso 7 no hace fallar el job: si un servicio no queda saludable, el pipeline termina igual pero muestra el aviso y los logs. Revise siempre la salida del job tras un despliegue.

Valores válidos de SERVICE_NAME ​

StackServicios
idpauth-backend, auth-authorization-frontend, auth-frontend-admin, frontend-public-auth
seleccionadorbackend-seleccion-postulantes, frontend-portal-admin-seleccion-postulantes, frontend-portal-comisiones-seleccion-postulantes, frontend-portal-postulantes-seleccion-postulantes, frontend-estacion-examen
docsfrontend-manual-tecnico, frontend-manual-de-usuario

Ejecución manual ​

Despliegue completo de un entorno (todos los servicios), desde una máquina con acceso a GitLab:

sh
curl -fsSk --retry 3 -X POST \
  --form token=$INFRA_DEPLOY_TRIGGER_TOKEN \
  --form ref=develop \
  "https://git.eje.gob.bo/api/v4/projects/13/trigger/pipeline"

Despliegue de un solo servicio:

sh
curl -fsSk --retry 3 -X POST \
  --form token=$INFRA_DEPLOY_TRIGGER_TOKEN \
  --form ref=main \
  --form "variables[SERVICE_NAME]=auth-backend" \
  "https://git.eje.gob.bo/api/v4/projects/13/trigger/pipeline"

También puede ejecutarse desde GitLab: Build → Pipelines → Run pipeline en infra-deploy, eligiendo la rama y, opcionalmente, la variable SERVICE_NAME.

Agregar un nuevo servicio ​

  1. Crear el Dockerfile (con EXPOSE 80 y endpoint /health/) en el repositorio del servicio.
  2. Agregar los jobs build y trigger-infra-deploy con su SERVICE_NAME.
  3. Agregar el servicio al docker-compose.yml del stack correspondiente en infra-deploy, con un puerto libre.
  4. Agregar el nombre en el case del despliegue selectivo y en el bloque de despliegue completo de .gitlab-ci.yml de infra-deploy.
  5. Actualizar el mapa de subdominios y puertos.

Custodio ​

custodio tiene un pipeline independiente que no despliega en servidores: genera ejecutables portables y los publica en el Package Registry de GitLab. Se dispara al crear un tag Git.

StageJobRunnerDescripción
testtesttestpytest tests/ sobre la lógica de negocio (python:3.12-slim)
testtest:gui-importtestSmoke test: inicializa la GUI Tkinter bajo Xvfb
buildbuild:linuxtestPyInstaller con Custodio.spec en debian:bookworm-slim → Custodio-linux-<tag>
buildbuild:windowswindowsPyInstaller en runner Windows nativo → Custodio-windows-<tag>.exe
verifychecksumstestGenera CHECKSUMS.txt (SHA-256 de ambos ejecutables)
publishpublishtestManual. Sube ejecutables y checksums a packages/generic/custodio/<tag>/
  • Los jobs de test corren automáticamente con tags; en merge requests y en main quedan como manuales.
  • Los artefactos se conservan 6 meses.
  • publish es un paso manual deliberado para controlar qué versión se distribuye.

Variables y secretos de CI/CD ​

Las variables se configuran en GitLab en Settings → CI/CD → Variables de cada proyecto. Ningún secreto se guarda en los repositorios.

Convención de prefijos:

  • DEV_*: entorno de test (rama develop).
  • PROD_*: entorno de producción (rama main).

WARNING

Las variables PROD_* deben marcarse como Protected (solo disponibles en ramas protegidas, como main) y los secretos como Masked para que no aparezcan en los logs de los jobs.

Variables predefinidas de GitLab ​

Usadas por los jobs build; no requieren configuración:

VariableUso
CI_REGISTRY, CI_REGISTRY_IMAGERegistry y ruta de la imagen del proyecto
CI_REGISTRY_USER, CI_REGISTRY_PASSWORDLogin al registry desde el job
CI_COMMIT_BRANCH, CI_COMMIT_REF_SLUGRama y su versión normalizada para tags
CI_COMMIT_SHORT_SHASHA corto para el tag inmutable
CI_COMMIT_TAG, CI_JOB_TOKEN, CI_API_V4_URLPipeline de Custodio

Repositorios de servicio ​

Todos ​

VariableTipoDescripción
INFRA_DEPLOY_TRIGGER_TOKENVariable, MaskedToken de trigger del proyecto infra-deploy

TIP

Es recomendable definir INFRA_DEPLOY_TRIGGER_TOKEN a nivel del grupo proceso-seleccion para no repetirla en cada repositorio.

Frontends ​

Cada frontend requiere las variables de build de su tabla en Imágenes Docker, en ambas versiones:

DEV_VITE_<NOMBRE>    → build de develop (test)
PROD_VITE_<NOMBRE>   → build de main (producción)

Ejemplo para frontend-portal-admin-seleccion-postulantes:

TestProducción
DEV_VITE_TITLE_APPPROD_VITE_TITLE_APP
DEV_VITE_API_ENDPOINTPROD_VITE_API_ENDPOINT
DEV_VITE_API_ENDPOINT_PATHPROD_VITE_API_ENDPOINT_PATH
DEV_VITE_API_ENDPOINT_FILESPROD_VITE_API_ENDPOINT_FILES
DEV_VITE_AUTH_ORIGIN_PORTALPROD_VITE_AUTH_ORIGIN_PORTAL
DEV_VITE_API_FILES_PRIVATEPROD_VITE_API_FILES_PRIVATE
DEV_VITE_MANUAL_USUARIO_URLPROD_VITE_MANUAL_USUARIO_URL
DEV_VITE_MANUAL_TECNICO_URLPROD_VITE_MANUAL_TECNICO_URL
DEV_VITE_AUTH_PUBLIC_URLPROD_VITE_AUTH_PUBLIC_URL

Estas variables no son secretas: terminan incrustadas en el JavaScript público.

Orquestador (infra-deploy) ​

Todas existen con prefijo DEV_ y PROD_, salvo las del registry.

Host de despliegue ​

Variable (sin prefijo)TipoDescripción
DEPLOY_HOSTVariableIP o nombre del host del entorno
DEPLOY_USERVariableUsuario SSH en el host
SSH_PRIVATE_KEYVariable, ProtectedContenido de la clave privada SSH (se carga con ssh-add)
DEPLOY_BASE_PATHVariableDirectorio base de los stacks en el host

El usuario de despliegue debe poder ejecutar docker sin sudo en el host.

IDP (auth-backend) ​

Variable (sin prefijo)TipoSe escribe en back.env como
IDP_BACK_ENVFile, ProtectedBase del archivo (variables no secretas)
IDP_SECRET_KEYVariable, Masked+ProtectedSECRET_KEY
IDP_SIGNING_KEY_MASTER_SECRETVariable, Masked+ProtectedSIGNING_KEY_MASTER_SECRET
IDP_DATABASE_PASSVariable, Masked+ProtectedDATABASE_PASS

Seleccionador (backend-seleccion-postulantes) ​

Variable (sin prefijo)TipoSe escribe en back.env como
SELECCIONADOR_BACK_ENVFile, ProtectedBase del archivo (variables no secretas)
SELECCIONADOR_SECRET_KEYVariable, Masked+ProtectedSECRET_KEY
SELECCIONADOR_SIGNING_KEY_MASTER_SECRETVariable, Masked+ProtectedSIGNING_KEY_MASTER_SECRET
SELECCIONADOR_DATABASE_PASSVariable, Masked+ProtectedDATABASE_PASS

Registry (compartidas por ambos entornos) ​

VariableTipoDescripción
REGISTRY_DEPLOY_TOKEN_USERVariableUsuario del deploy token del grupo
REGISTRY_DEPLOY_TOKEN_PASSWORDVariable, MaskedToken con permiso read_registry

Variable de trigger ​

VariableOrigenDescripción
SERVICE_NAMEEnviada por trigger-infra-deploy o manualmenteServicio a desplegar. Si se omite, se despliegan todos

Archivo back.env ​

El archivo final que reciben los backends es la concatenación de <SISTEMA>_BACK_ENV + los tres secretos individuales. Se monta en el contenedor como env_file y como /app/.env (solo lectura).

  • Para cambiar una variable no secreta: editar la variable de tipo File<PREFIJO>_<SISTEMA>_BACK_ENV en GitLab y volver a desplegar.
  • Para cambiar un secreto: editar la variable individual y volver a desplegar.
  • Los cambios hechos directamente en el host se pierden en el siguiente despliegue.

WARNING

back.env debe incluir SYSTEM_STATUS=True. Con SYSTEM_STATUS en False (valor por defecto en manage.py), los comandos de gestión distintos de check, runserver, help, version y shell se omiten, incluidas las migraciones del arranque. Ver Scripts útiles de los backends.

Custodio ​

No requiere variables personalizadas: usa CI_JOB_TOKEN para publicar en el Package Registry del propio proyecto. Requiere un runner Windows registrado con el tag windows y Python con Tkinter.

Scripts útiles de los backends ​

Comandos de gestión y scripts incluidos en auth-backend y backend-seleccion-postulantes. Se agrupan según su propósito:

  • Arranque y configuración: se ejecutan en cada despliegue o una única vez.
  • Datos de prueba y emulaciones: solo para el entorno de test.
  • Importación / exportación: migración de datos y configuración.
  • Mantenimiento y servicios: tareas periódicas.
  • Utilidades de desarrollo: diagramas y auditorías.

Ejecución en los contenedores ​

Desde el directorio del stack en el host (${DEPLOY_BASE_PATH}/idp o ${DEPLOY_BASE_PATH}/seleccionador):

sh
# Comando de gestión de Django
docker compose exec auth-backend python manage.py <comando> [opciones]
docker compose exec backend-seleccion-postulantes python manage.py <comando> [opciones]

# Script de la carpeta scripts/ (django-extensions runscript)
docker compose exec backend-seleccion-postulantes \
  python manage.py runscript <script> [--script-args <arg1> <arg2> ...]

Los scripts de scripts/ definen una función run() y se ejecutan con runscript (django-extensions, incluido en requirements.prod.txt); no funcionan con python scripts/<script>.py.

Para copiar archivos generados entre el contenedor y el host:

sh
docker compose cp backend-seleccion-postulantes:/app/<ruta> ./<destino>
docker compose cp ./<origen> backend-seleccion-postulantes:/app/<ruta>

Requisito: SYSTEM_STATUS ​

En ambos backends, manage.py lee SYSTEM_STATUS del .env (por defecto False). Si es False, solo se permiten check, runserver, help, version y shell; cualquier otro comando (incluidos migrate, runscript y todos los de esta página) imprime [STANDBY] '<comando>' omitido y termina sin hacer nada.

auth-backend ​

Arranque y configuración ​

Los tres primeros se ejecutan automáticamente en entrypoint.sh en cada arranque del contenedor.

ComandoArgumentosDescripción
rotate_signing_keys—Ejecuta la rotación del ciclo de vida de las claves de firma de tokens. Transaccional.
create_default_scopes—Crea los scopes OAuth2 definidos en DEFAULT_SCOPES que no existan. No modifica los existentes.
create_claim_definitions—Crea/activa las definiciones de claims de ENABLED_PRESET_CLAIM_DEFINITIONS y desactiva las que no estén en esa lista.
create_super_adminVer abajoCrea un usuario administrador interno. Se usa una vez al inicializar un entorno.

create_super_admin ​

sh
docker compose exec auth-backend python manage.py create_super_admin \
  --username <usuario> \
  --password '<contraseña>' \
  --first_name <nombre> \
  --last_name <apellido>
OpciónRequeridoDescripción
--usernameUno de --username o --emailIdentificador de login
--emailUno de --username o --emailIdentificador de login
--passwordSíDebe cumplir la política de contraseñas
--first_nameSíNombre
--last_nameSíApellido
  • Crea IdentityUser (is_admin=True, usuario interno), LoginIdentifier, UserCredential y PasswordCredential.
  • Si el identificador ya existe, no crea nada.

WARNING

La contraseña queda en el historial de la shell. Cámbiela tras el primer ingreso o limpie el historial.

Datos de prueba ​

create_postulantes_prueba ​

Solo para entornos de prueba. Crea N cuentas de postulante externo en el IDP y escribe un JSON con sus datos (incluida la contraseña en texto plano), que sirve como fuente de identidades para el seeder de postulaciones del backend seleccionador.

sh
docker compose exec auth-backend python manage.py create_postulantes_prueba 500 \
  --output /app/private_files/seeders/postulantes_prueba.json \
  --password-fija 'Prueba123!' \
  --confirmo-entorno-de-prueba
Argumento / opciónRequeridoDefaultDescripción
cantidad (posicional)Sí—Cantidad de cuentas a crear (> 0)
--outputSí—Ruta del JSON de salida (crea el directorio si no existe)
--prefijoNopostulante.pruebaPrefijo del username (<prefijo>.<0001>.<hex6>)
--seedNo—Semilla de Faker/random para reproducir el lote
--password-fijaNo—Misma contraseña para todo el lote (mín. 8, una mayúscula, un dígito y uno de !@#$%*)
--confirmo-entorno-de-pruebaSí—Confirmación explícita; sin ella el comando falla

Crea por cada cuenta: IdentityUser, LoginIdentifier (username), atributos de documento de identidad, tipo de documento y fecha de nacimiento, UserCredential y PasswordCredential. El email generado usa el dominio postulante-prueba.eje.gob.bo.

TIP

Escriba el JSON dentro de /app/private_files/ para que quede en el volumen storage/private del host; fuera de los volúmenes se pierde al recrear el contenedor.

Mantenimiento y servicios ​

Envío de notificaciones pendientes (incluye seguimiento de Ciudadanía Digital) mediante EnvioNotificacionDispatcher:

ComandoOpcionesModo de uso
notificacion_sender_cron—Procesa una vez y termina. Pensado para cron / programador de tareas.
notification_sender_service--interval N (default 30 s)Bucle continuo; termina ordenadamente con SIGTERM/SIGINT. Pensado como servicio de larga duración.
sh
docker compose exec auth-backend python manage.py notificacion_sender_cron

backend-seleccion-postulantes ​

Arranque ​

entrypoint.sh lanza en segundo plano un bucle que ejecuta cada 30 minutos:

limpiar_segmentos_huerfanos ​

Elimina los segmentos de subidas de documentos abandonadas a mitad de carga (usuario que cierra la pestaña o cancela): selecciona DocumentoUsuario sin archivo final cuyo último segmento tenga más de 3 horas y borra sus segmentos. Sin argumentos.

sh
docker compose exec backend-seleccion-postulantes python manage.py limpiar_segmentos_huerfanos

Datos de prueba y emulaciones ​

Flujo típico para preparar una convocatoria completa en el entorno de test:

Los emuladores procesan lo que esté pendiente en el momento en que se ejecutan, por lo que la convocatoria debe estar en la fase correspondiente, con comisión conformada y sesión activa.

create_empty_convocatoria (runscript) ​

Crea una convocatoria de prueba usando seeders/convocatorias/main.py (crear_convocatoria_prueba).

sh
docker compose exec backend-seleccion-postulantes \
  python manage.py runscript create_empty_convocatoria

# Con argumentos opcionales (posicionales)
docker compose exec backend-seleccion-postulantes \
  python manage.py runscript create_empty_convocatoria \
  --script-args <area_jurisdiccional> <caracter> <no_usado> <forma_presentacion>

Sin argumentos usa los valores por defecto de crear_convocatoria_prueba.

seeder ​

Genera postulaciones de prueba (usuarios, perfiles, CV, documentos y postulaciones) para una convocatoria existente.

sh
docker compose exec backend-seleccion-postulantes \
  python manage.py seeder --convocatoria 109 --cantidad 50
OpciónDefaultDescripción
--convocatoriaPostulacionSeederConfig.CONVOCATORIA_ID_DEFAULTID de la convocatoria (debe existir)
--cantidad10Cantidad de postulaciones
--no-transactiondesactivadoSin transacción; ante error quedan datos parciales (depuración)
--quietdesactivadoSin logs detallados
--resumendesactivadoMuestra el resumen de postulaciones existentes y termina

Requisitos:

  • Catálogos cargados (provincias, municipios, etc.) y la convocatoria creada.
  • Archivo de identidades seeders_data/test_500_1.json (relativo a /app). Para usar cuentas generadas con create_postulantes_prueba, copie el JSON al contenedor en esa ruta.

Salida:

  • Exporta un XLSX de credenciales en seeders_data/credenciales_conv{ID}.xlsx dentro del almacenamiento público (PUBLIC_MEDIA_ROOT). Se sobrescribe en cada ejecución para la misma convocatoria.

DANGER

El XLSX contiene contraseñas en texto plano y se guarda en el almacenamiento público, que nginx sirve en /files/. No ejecute el seeder en producción y elimine el archivo cuando ya no sea necesario.

emular_dmh ​

Emula la Revisión de Documentación Mínima Habilitante de una comisión.

sh
docker compose exec backend-seleccion-postulantes \
  python manage.py emular_dmh --comision <ID>

Por cada miembro responsable (o subcomisión) con postulaciones asignadas en la sesión activa:

  1. Inicia la revisión individual de cada postulación pendiente.
  2. Marca aleatoriamente (≈1 de cada 3) un respaldo requerido como "no cumple".
  3. Finaliza la revisión y solicita la aprobación de los miembros participantes.

Las aprobaciones usan un cliente simulado (FakeIDPApprovalClient) que no llama a servicios externos y las da por aprobadas.

emular_cme ​

Emula la Calificación de Méritos de una comisión. Mismo flujo que emular_dmh: por cada postulación pendiente califica las acreditaciones curriculares considerando la cantidad declarada y descartando aleatoriamente algunas (≈1 de cada 3), finaliza la calificación y solicita aprobaciones con el cliente simulado.

sh
docker compose exec backend-seleccion-postulantes \
  python manage.py emular_cme --comision <ID>

emular_rendicion_examen ​

Emula la rendición del examen de admisión en línea.

sh
docker compose exec backend-seleccion-postulantes \
  python manage.py emular_rendicion_examen --comision <ID>

Requisitos: la comisión debe ser de recepción de examen de admisión y la programación del examen debe estar en modalidad en línea; de lo contrario el comando falla.

Por cada recinto habilitado con miembro responsable:

  1. Asegura estaciones de examen habilitadas para toda la capacidad del recinto.
  2. Inicia cada sesión programada pendiente.
  3. Registra asistencia (probabilidad 90 % por postulante).
  4. Rinde el examen de cada asistente eligiendo opciones al azar.
  5. Fuerza el fin de la rendición, actualiza las rendiciones finalizadas y cierra la sesión.

No pasa por el registro JWT de estaciones ni por sesiones de comisión.

Mutaciones GraphQL de seeders ​

El esquema de administración expone seedConvocatoriaPrueba y seedPostulacionesPrueba(convocatoria, cantidad), que reutilizan la misma lógica. Solo funcionan con DEBUG=True y no requieren sesión. seedPostulacionesPrueba se ejecuta en un hilo en segundo plano y devuelve de inmediato la URL del XLSX de credenciales.

WARNING

Mantenga DEBUG=False en producción: con DEBUG=True estas mutaciones quedan accesibles sin autenticación.

Correcciones de datos (runscript) ​

ScriptDescripción
simular_periodos_impugnacionPara cada informe de cierre de fase con resultados publicados (en fases que admiten impugnación) sin periodo de impugnación, crea uno con recepción abierta hasta 30 días desde ahora. Completa la fecha de publicación si falta.
set_origenes_impugnadosMarca como impugnado el origen de cada resultado de fase que tenga una impugnación asociada.
clean_variables_plantillaElimina las variables de plantillas SGC cuya clave no esté en CLAVES_VAR_CHOICES.
sh
docker compose exec backend-seleccion-postulantes \
  python manage.py runscript simular_periodos_impugnacion

Importación / exportación ​

Configuración SGC ​

Exporta e importa la configuración del Sistema de Gestión de Calidad (procedimientos, registros/plantillas, formatos de CV, versiones, plazos, comisiones y parámetros). Útil para trasladar la configuración entre entornos.

sh
# Exportar
docker compose exec backend-seleccion-postulantes \
  python manage.py exportar_configuracion_sgc /app/private_files/sgc_export

# Importar (probar primero con --dry-run)
docker compose exec backend-seleccion-postulantes \
  python manage.py importar_configuracion_sgc /app/private_files/sgc_export \
  --usuario <email> --dry-run
ComandoArgumentos
exportar_configuracion_sgccarpeta (posicional): carpeta de salida
importar_configuracion_sgccarpeta (posicional); --usuario <email> (requerido, UsuarioBase que registra la importación); --dry-run (revierte todo al final)

La importación es transaccional: crea versiones nuevas y pasa las vigentes al historial. En --dry-run o ante error, elimina además los archivos subidos (el rollback de base de datos no cubre el almacenamiento).

Para mover la exportación entre entornos, cópiela al host con docker compose cp (o tómela de storage/private/ si se escribió en /app/private_files).

Datos históricos ​

Migran convocatorias de sistemas anteriores desde backups SQL Server (configuración de conexión en datos_historicos/services/conexion_backups).

ComandoOpcionesDescripción
comparar_esquemas—Analiza los esquemas de las bases históricas y escribe el resultado en datos_historicos/analisis_esquema
exportar_datos_historicos--familia F (por defecto ambas), --anio A (requiere --familia), --salida DIR (default private_files/exports_json)Lee las bases de backup y escribe un JSON por convocatoria. No modifica la base de Django
importar_datos_historicos--archivo FILE, --carpeta DIR (default private_files/exports_json), --reemplazarImporta los JSON. Sin --reemplazar omite convocatorias ya importadas

DANGER

importar_datos_historicos --reemplazar borra en cascada la convocatoria existente antes de importarla.

Utilidades de desarrollo ​

Pensadas para el entorno local de desarrollo, no para los contenedores.

Script (runscript)BackendDescripción
generar_graficasAmbosGenera diagramas de modelos (graph_models) por app y globales en diagramas/. Instala graphviz con sudo apt-get, por lo que no funciona dentro del contenedor.
auditar_types_graphqlSeleccionadorCompara lo que expone un schema GraphQL con lo que consulta su frontend y genera un reporte Markdown.
sh
python manage.py runscript auditar_types_graphql \
  --script-args <schema> <carpeta_front> <salida.md>

<schema>: admin, comision, postulante o estacion_examen.

Operación en el host ​

Comandos para inspeccionar y operar los servicios directamente en el host de un entorno. Todos se ejecutan desde el directorio del stack:

sh
cd ${DEPLOY_BASE_PATH}/idp            # auth-backend y frontends del IDP
cd ${DEPLOY_BASE_PATH}/seleccionador  # backend y frontends del SPS
cd ${DEPLOY_BASE_PATH}/docs           # manuales

INFO

La forma normal de desplegar es mediante los pipelines. Las operaciones manuales de esta página son para diagnóstico y contingencias; el siguiente despliegue vuelve a aplicar la configuración de infra-deploy.

Estado de los servicios ​

sh
docker compose ps

Columna STATUS:

EstadoSignificado
Up (healthy)Contenedor en ejecución y /health/ responde
Up (health: starting)Dentro del start_period (20 s backends, 5 s frontends)
Up (unhealthy)En ejecución, pero el health check falló 3 veces seguidas
RestartingEl proceso termina y Docker lo reinicia (restart: unless-stopped)

Detalle del último health check:

sh
docker inspect --format '{{json .State.Health}}' auth-backend

Uso de recursos:

sh
docker stats --no-stream

Logs ​

sh
# Últimas 200 líneas
docker compose logs --tail 200 backend-seleccion-postulantes

# En vivo
docker compose logs -f backend-seleccion-postulantes

# Desde hace 30 minutos
docker compose logs --since 30m auth-backend

En los backends, la salida incluye los logs de acceso y error de gunicorn y la salida del entrypoint.sh (migraciones, collectstatic y comandos de arranque). Los logs rotan a 10 MB con 5 archivos por contenedor.

Reiniciar o recrear un servicio ​

sh
# Reiniciar el proceso (misma imagen y configuración)
docker compose restart auth-backend

# Recrear el contenedor (relee back.env y .env)
docker compose up -d --force-recreate --no-deps auth-backend

TIP

restart no vuelve a leer env_file. Si cambió back.env, use up -d --force-recreate.

Actualizar a la última imagen ​

Equivale a lo que hace el pipeline para un servicio:

sh
docker compose pull backend-seleccion-postulantes
docker compose up -d --no-deps backend-seleccion-postulantes

Requiere sesión iniciada en el registry (docker login git.eje.gob.bo:5050) con el deploy token del grupo.

Rollback a una versión anterior ​

Cada build publica un tag inmutable <rama>-<sha corto>. Para volver a una versión anterior de un servicio:

  1. Identificar el tag en GitLab: Deploy → Container Registry del repositorio, o el SHA del commit en el historial de la rama.

  2. Desplegar ese tag solo para el servicio afectado, sobrescribiendo IMAGE_TAG en la línea de comandos (tiene prioridad sobre .env):

    sh
    IMAGE_TAG=main-a1b2c3d4 docker compose pull auth-backend
    IMAGE_TAG=main-a1b2c3d4 docker compose up -d --no-deps auth-backend
  3. Verificar con docker compose ps y los logs.

Para revertir el rollback, vuelva a desplegar con latest:

sh
docker compose pull auth-backend
docker compose up -d --no-deps auth-backend

WARNING

  • El siguiente despliegue del servicio por pipeline vuelve a <rama>-latest. Para que el rollback sea definitivo, revierta el commit en la rama y deje que el pipeline publique una nueva imagen.
  • En los backends, entrypoint.sh aplica migraciones al arrancar, pero volver a una imagen anterior no revierte migraciones. Si la versión nueva incluía migraciones incompatibles, evalúe revertirlas con python manage.py migrate <app> <migracion> antes del rollback.

Ejecutar comandos dentro de un contenedor ​

sh
# Shell interactiva (los backends corren como appuser)
docker compose exec backend-seleccion-postulantes bash

# Comando puntual
docker compose exec auth-backend python manage.py showmigrations

Los frontends usan nginx:alpine, que solo incluye sh:

sh
docker compose exec frontend-portal-admin-seleccion-postulantes sh

Los comandos de gestión disponibles se describen en Scripts útiles de los backends.

Archivos de los backends ​

Los archivos de los backends persisten en el host, fuera de los contenedores:

${DEPLOY_BASE_PATH}/idp/storage/{public,private}
${DEPLOY_BASE_PATH}/seleccionador/storage/{public,private}

Recrear o eliminar un contenedor no afecta estos directorios. Inclúyalos en el respaldo del entorno junto con las bases de datos.

Detener servicios ​

sh
# Detener un servicio (no se reinicia automáticamente)
docker compose stop frontend-estacion-examen

# Volver a iniciarlo
docker compose start frontend-estacion-examen

DANGER

Evite docker compose down -v en los stacks: aunque los volúmenes actuales son directorios del host (bind mounts), el comando elimina contenedores y redes del stack y deja todos los servicios fuera de línea hasta un nuevo despliegue.

Limpieza de imágenes ​

Cada despliegue descarga nuevas imágenes; las anteriores quedan sin etiqueta y ocupan disco:

sh
docker image prune -f

Para eliminar también las imágenes no usadas por ningún contenedor (incluye tags antiguos que podrían servir para rollback):

sh
docker image prune -a -f

Repositorios del sistema ​

El sistema está compuesto por repositorios independientes alojados en el grupo proceso-seleccion de GitLab (git.eje.gob.bo).

Inventario ​

RepositorioTipoTecnología principalDescripción
auth-backendBackendPython 3.11 · Django 5.2Proveedor de identidad: OAuth2/OIDC, cuentas, aprobación de documentos y notificaciones
auth-authorization-frontendFrontendVue 3 · ViteLogin, registro y consentimiento OIDC
auth-frontend-adminFrontendVue 3 · ViteAdministración del IDP
frontend-public-authFrontendVue 3 · ViteAutogestión de la cuenta del usuario
backend-seleccion-postulantesBackendPython 3.11 · Django 5.2Lógica del proceso de selección de postulantes
frontend-portal-admin-seleccion-postulantesFrontendVue 3 · VitePortal administrativo del proceso de selección
frontend-portal-comisiones-seleccion-postulantesFrontendVue 3 · VitePortal de las comisiones
frontend-portal-postulantes-seleccion-postulantesFrontendVue 3 · VitePortal de los postulantes
frontend-estacion-examenFrontendVue 3 · ViteEstación de rendición del examen en línea
frontend-manual-tecnicoDocumentaciónVitePressEste manual
frontend-manual-de-usuarioDocumentaciónVitePressManual de usuario
custodioAplicación escritorioPython 3.12 · TkinterValidación, consolidación y cifrado de bancos de preguntas
infra-deployInfraestructuraDocker Compose · GitLab CIOrquestación del despliegue (ver Infraestructura y despliegue)

Herramientas para el entorno de desarrollo ​

HerramientaVersiónUso
Git—Control de versiones
Python3.11 (backends), 3.12 (Custodio)Backends y Custodio
Node.js22.x o 24.xFrontends y manuales
pnpm11.3.0 (vía Corepack)Gestor de paquetes de los proyectos Node
PostgreSQL—Base de datos de los backends
Docker + Compose—Construcción y prueba de imágenes
Graphviz—Generación de diagramas de modelos (opcional)

Activación de pnpm (incluido en Node.js mediante Corepack):

sh
corepack enable pnpm

La versión exacta de pnpm queda fijada por el campo packageManager de cada package.json.

Backends: estructura ​

Aplica a auth-backend (IDP) y backend-seleccion-postulantes (SPS). Ambos son proyectos Django 5.2 con la misma organización.

Organización general ​

text
<backend>/
├── <proyecto>/            # Configuración del proyecto Django
│   ├── settings.py        # Configuración (lee variables de .env)
│   ├── urls.py            # Rutas raíz: schemas GraphQL, /api/v1/, docs, health
│   ├── *_schema.py        # Composición de cada schema GraphQL
│   ├── api/ | api_router.py  # Router de la API REST /api/v1/
│   ├── middleware/        # StandbyMiddleware y otros
│   └── wsgi.py
├── <app>/                 # Una carpeta por módulo funcional (app Django)
├── scripts/               # Scripts para manage.py runscript
├── diagramas/             # Diagramas de modelos generados
├── manage.py              # CLI de Django (con control SYSTEM_STATUS)
├── requirements.txt       # Dependencias de desarrollo
├── requirements.prod.txt  # Dependencias de la imagen Docker
├── .env.example           # Plantilla de variables de entorno
├── Dockerfile · entrypoint.sh · nginx.conf
BackendCarpeta del proyecto
auth-backendauth_interop_backend/
backend-seleccion-postulantesseleccion_backend/

Estructura de una app ​

Cada app separa sus modelos, sus servicios y lo que expone en cada schema GraphQL:

text
<app>/
├── models.py               # Modelos (tablas)
├── migrations/             # Migraciones de base de datos
├── services/               # Lógica de negocio (managers, conectores, utilidades)
├── admin_schema/           # Queries/mutations del schema administrativo
├── comision_schema/        # ... del schema de comisiones (SPS)
├── postulante_schema/      # ... del schema de postulantes (SPS)
├── estacion_examen_schema/ # ... del schema de estaciones (SPS)
├── main_schema/ · public_schema/  # ... schemas del IDP
├── api/                    # API REST (IDP: views, serializers, openapi)
├── management/commands/    # Comandos de manage.py
├── templates/              # Plantillas (correos, documentos)
├── urls.py · views.py      # Endpoints HTTP no GraphQL
└── admin.py

Solo existen las carpetas de los schemas en los que participa la app. La lógica de negocio vive en services/; los resolvers GraphQL y las vistas REST la invocan.

Apps del IDP (auth-backend) ​

AppResponsabilidad
iam_coreIdentidades: usuarios, identificadores de login, credenciales (contraseña, TOTP), proveedores de identidad federados, claims personalizados, registro
authenticationFlujo de inicio de sesión (intentos de login, vinculación de identidades federadas)
oauth2_ejeServidor OAuth2: clientes, scopes, códigos de autorización, consentimientos, tokens y claves de firma
oidcOpenID Connect: discovery, JWKS, userinfo y definición de claims por scope
aprobacion_documentosAprobación de documentos oficiales con firma digital o Ciudadanía Digital
notificacionesSolicitudes de notificación, destinatarios y envíos
serve_private_filesEntrega controlada de archivos privados

Apps del SPS (backend-seleccion-postulantes) ​

AppResponsabilidad
iam_coreUsuario local vinculado al IDP (UsuarioBase), accesos por portal, privilegios y sesiones
catalogosCatálogos geográficos (provincias, municipios)
analitica_planillasPlanillas del Órgano Judicial y acefalías de jueces
acciones_previasInformes técnicos previos, analítica de acefalías y propuesta de plazas
sgcSistema de Gestión de Calidad: procedimientos, registros, plantillas, formatos de CV, plazos y parámetros
convocatoriasConvocatorias, ofertas departamentales, requisitos, méritos, fases y programación del examen
resolucionesResoluciones administrativas y ampliaciones de plazo
difusionAvisos y archivos publicados en el sitio web institucional
postulacionesDatos del postulante, currículum, acreditaciones y postulaciones
comisionesComisiones, subcomisiones, miembros, excusas y veedores
trabajo_comisionesTrabajo de las comisiones: sesiones, revisión DMH, calificación de méritos, examen de admisión y resultados por fase
impugnacionesPeriodos de impugnación e impugnaciones de resultados
conclusion_procesoInforme final y postulantes admitidos
reportesCertificados de participación y reportes
document_engineDocumentos de usuario, subida por segmentos y tokens de acceso a documentos
aprobacion_documentosIntegración con la aprobación de documentos del IDP
notificacionesIntegración con las notificaciones del IDP
custodio_integrationVersiones de Custodio y cifrado/descifrado de bancos de preguntas
interoperabilidadAPI REST externa y servicios interoperables
datos_historicosConvocatorias y postulaciones de sistemas anteriores
consultasPreguntas frecuentes y casos de ayuda
seedersDatos de prueba y emuladores (solo entornos de prueba)

Paquetes de soporte (no son apps con modelos):

PaqueteUso
idp_api_clientCliente de las APIs REST del IDP (directorio, aprobación, notificaciones)
website_apiCliente de la API del sitio web institucional (avisos y archivos)
serve_private_filesEntrega controlada de archivos privados
fixturesDatos iniciales (servicios de interoperabilidad externa)

El detalle de las tablas de cada app se describe en la sección de modelado de datos.

Backends: dependencias y configuración ​

Dependencias ​

Versiones de requirements.prod.txt (las que se instalan en la imagen Docker).

Comunes a ambos backends ​

PaqueteVersiónUso
django5.2.14 (IDP) · 5.2.16 (SPS)Framework web
graphene-django3.2.2APIs GraphQL sobre los modelos de Django
graphene-file-upload1.2.2Subida de archivos en GraphQL (multipart)
django-graphbox1.5.14Construcción de schemas GraphQL y middleware de autenticación
djangorestframework3.17.1API REST /api/v1/
drf-spectacular0.29.0Generación de OpenAPI, Swagger UI y ReDoc
django-extensions3.1.3runscript, graph_models y utilidades de desarrollo
django-environ0.7.0Lectura de variables desde .env
wsgicors0.7.0Cabeceras CORS a nivel WSGI
psycopg[binary]3.3.4Driver de PostgreSQL
gunicorn26.0.0Servidor WSGI en producción
cryptography48.0.0Primitivas criptográficas
PyJWT2.10.1Emisión y validación de JWT
pyotp2.9.0Códigos TOTP (segundo factor)
requests2.34.2Cliente HTTP para servicios externos
Pillow9.5.0Procesamiento de imágenes
qrcode8.2Generación de códigos QR
pypdf6.14.2Lectura y manipulación de PDF
Faker37.11.0 (IDP) · ≥18.0.0 (SPS)Datos ficticios para comandos de prueba

Solo IDP (auth-backend) ​

PaqueteVersiónUso
captcha0.7.1Captcha de imagen en el flujo de login
pyHanko0.35.2Firmas digitales en PDF. Declarado en requirements; no se encontró uso directo en el código

Solo SPS (backend-seleccion-postulantes) ​

PaqueteVersiónUso
weasyprint69.0Generación de PDF desde HTML
Jinja23.1.6Plantillas de documentos
tiptapy0.21.0Conversión de contenido del editor Tiptap a HTML
beautifulsoup44.9.3Procesamiento de HTML
openpyxl3.1.5Lectura y escritura de Excel
pdf2image1.16.0Conversión de PDF a imagen
opencv-contrib-python4.8.1.78Lectura OMR de hojas de respuestas y marcadores ArUco en documentos
numpy1.24.3Cálculo numérico (requerido por OpenCV)
PyNaCl1.6.2Cifrado de bancos de preguntas (compatible con Custodio)
zipstream-ng1.9.3Descarga de archivos ZIP en streaming
pycountry26.2.16Catálogo de países
num2words0.5.14Números en letras para documentos

Dependencias de sistema ​

La imagen Docker instala además:

  • Ambos: nginx, curl.
  • SPS: ghostscript y librerías nativas de WeasyPrint y OpenCV (Pango, Cairo, GDK-PixBuf, libffi, libGL, fuentes Liberation y DejaVu). Ver Imágenes Docker.

requirements.txt frente a requirements.prod.txt ​

requirements.txt es para desarrollo local: no incluye gunicorn y agrega mysqlclient (driver MySQL, requiere las librerías de desarrollo de MySQL en el sistema). En el SPS también fija Pillow>=13.0.0. La imagen Docker usa solo requirements.prod.txt.

Variables de entorno ​

Los backends leen su configuración de un archivo .env en la raíz del proyecto (django-environ). La plantilla es .env.example. En los servidores, este archivo es el back.env que genera el pipeline (ver Variables y secretos de CI/CD).

Comunes ​

VariableDescripción
SECRET_KEYClave secreta de Django
DEBUGModo depuración. Debe ser False en servidores
SYSTEM_STATUSTrue para operar; False activa el modo standby (503) y bloquea comandos de manage.py
ALLOWED_HOSTSDominios aceptados (lista separada por comas)
ALLOWED_CORSOrígenes de los frontends permitidos (CORS y CSRF)
MAIN_DOMAINDominio base para las cookies (sin subdominio)
DATABASE_ENGINEMotor de Django (django.db.backends.postgresql)
DATABASE_NAME · DATABASE_USER · DATABASE_PASS · DATABASE_HOST · DATABASE_PORTConexión a la base de datos
SIGNING_KEY_MASTER_SECRETClave maestra de cifrado de datos sensibles
STATIC_ROOTDestino de collectstatic (por defecto assets/)
PUBLIC_MEDIA_ROOTRaíz de archivos públicos (por defecto files/; en Docker /app/public_files)
PRIVATE_MEDIA_ROOTRaíz de archivos privados (por defecto private_files/; en Docker /app/private_files)
PRIVATE_MEDIA_SERVE_BACKENDCómo se entregan los archivos privados: django, apache o nginx (por defecto django)
PRIVATE_MEDIA_NGINX_HEADER · PRIVATE_MEDIA_NGINX_INTERNAL_PREFIXCabecera (X-Accel-Redirect) y prefijo interno (/private_files/) para nginx
PRIVATE_MEDIA_APACHE_HEADERCabecera para Apache (X-Sendfile)
PRIVATE_MEDIA_URLURL de archivos privados cuando los sirve Django

TIP

En los contenedores, nginx está configurado para X-Accel-Redirect sobre /private_files/, por lo que corresponde PRIVATE_MEDIA_SERVE_BACKEND=nginx.

Solo IDP ​

VariableDescripción
AUTHSERVER_ISSUERIssuer del servidor de autorización (tokens y discovery OIDC)
PUBLIC_LOGIN_URLURL pública del portal de login
IDP_CALLBACK_URLCallback del login con proveedores de identidad externos
IDP_LINKING_CALLBACK_URLCallback de vinculación de proveedores externos con una cuenta existente
IDP_LOGIN_CD_FOR_APPROVAL_CALLBACK_URLCallback del login con Ciudadanía Digital para aprobar documentos
CD_AGETIC_ISSUERIssuer de Ciudadanía Digital (AGETIC)
BASE_SERVE_PRIVATE_FILESURL base para construir enlaces a archivos privados
SSO_COOKIE_SESSION_KEYNombre de la cookie de sesión SSO

Solo SPS ​

VariableDescripción
DATABASE_CONN_MAX_AGEPersistencia de conexiones en segundos (por defecto 900)
IDP_OIDC_DISCOVERY_URLDiscovery OIDC del IDP
IDP_OIDC_CLIENT_ID · IDP_OIDC_CLIENT_SECRETCliente OIDC del SPS para el login de los portales
IDP_OIDC_SCOPESScopes solicitados en el login
IDP_OIDC_ADMIN_REDIRECT_URI · IDP_OIDC_ADMIN_LOGOUT_REDIRECT_URIRedirecciones de login/logout del portal administrativo
IDP_OIDC_COMISIONES_REDIRECT_URI · IDP_OIDC_COMISIONES_LOGOUT_REDIRECT_URIÍdem, portal de comisiones
IDP_OIDC_POSTULANTE_REDIRECT_URI · IDP_OIDC_POSTULANTE_LOGOUT_REDIRECT_URIÍdem, portal de postulantes
IDP_API_CLIENT_ID · IDP_API_CLIENT_SECRETCliente client_credentials para consumir las APIs REST del IDP
IDP_API_SCOPESScopes de ese cliente
IDP_API_BASE_URLURL base de las APIs del IDP
COOKIE_SESSION_KEYNombre de la cookie de sesión de los portales
COOKIE_ESTACION_SESSION_KEYNombre de la cookie de sesión de las estaciones de examen
CREDENTIALS_KEY_EXAMEN_VIRTUALClave de las credenciales JWT de estaciones de examen
APPROVALDOCS_APPROVAL_URLURL del IDP donde el usuario aprueba documentos
POST_APPROVALDOCS_POSTULACION_REDIRECT_URIRedirección tras aprobar documentos de una postulación
PUBLIC_DOCS_VERIFICATION_BASE_URLURL pública de verificación de documentos emitidos (códigos QR)
WEBSITE_API_BASE_URL · WEBSITE_API_KEYAPI del sitio web institucional (publicación de avisos)
MIGRATION_MSSQL_CONNConexión a los backups SQL Server de datos históricos (opcional)

Ejecución local ​

sh
# 1. Entorno virtual con Python 3.11
python3.11 -m venv env
source env/bin/activate

# 2. Dependencias
pip install -r requirements.txt

# 3. Configuración
cp .env.example .env   # completar valores; SYSTEM_STATUS=True

# 4. Base de datos
python manage.py migrate

# 5. Servidor de desarrollo
python manage.py runserver

En el IDP, después de migrar ejecute los comandos de inicialización que en Docker corren en el arranque (rotate_signing_keys, create_default_scopes, create_claim_definitions) y cree un administrador con create_super_admin. Ver Scripts útiles de los backends.

En el SPS, WeasyPrint requiere las librerías nativas en el sistema (en Debian/Ubuntu: libpango-1.0-0 libpangocairo-1.0-0 libgdk-pixbuf2.0-0 libffi-dev libcairo2 shared-mime-info fonts-liberation fonts-dejavu-core).

Frontends: estructura ​

Los siete frontends son aplicaciones de página única (SPA) con Vue 3, empaquetadas con Vite y con la misma organización base.

Organización general ​

text
<frontend>/
├── public/                # Archivos estáticos copiados tal cual al build
├── src/
│   ├── main.js            # Punto de entrada: crea la app, registra Pinia y el router
│   ├── App.vue            # Componente raíz
│   ├── assets/            # CSS (Tailwind), imágenes y fuentes
│   ├── components/        # Componentes reutilizables
│   │   └── ui/            # Componentes base (shadcn-vue / Reka UI), alias @ui
│   ├── composables/       # Lógica reutilizable (Composition API)
│   ├── configs/           # Variables de entorno, cliente Apollo y constantes
│   ├── graphql/           # Queries y mutations GraphQL
│   ├── lib/               # Utilidades (por ejemplo combinación de clases CSS)
│   ├── router/            # Rutas y guards de Vue Router
│   ├── shared/            # Utilidades y estado compartido entre módulos
│   ├── stores/            # Stores globales (Pinia)
│   └── views/             # Vistas asociadas a rutas, una carpeta por módulo
├── index.html             # HTML base donde Vite inyecta la aplicación
├── vite.config.js         # Plugins, alias y configuración de build
├── components.json        # Configuración de shadcn-vue
├── eslint.config.js · jsconfig.json
├── .env.example           # Plantilla de variables VITE_*
├── Dockerfile · nginx.conf
└── package.json · pnpm-lock.yaml · pnpm-workspace.yaml

Flujo de arranque ​

index.html carga src/main.js, que crea la aplicación con createApp, registra Pinia y el router, y monta App.vue. El router renderiza la vista de views/ que corresponde a la URL.

Carpetas propias de algunos proyectos ​

CarpetaProyectosUso
src/api/auth-authorization-frontendCliente HTTP (Fetch) de los endpoints REST del flujo de login
src/services/auth-authorization-frontendOrquestación de llamadas del flujo de login
src/auth/Portales del SPS, estación, frontend-public-authInicio de sesión vía IDP y manejo de sesión
src/jacobitus/frontend-public-authIntegración con Jacobitus (firma digital con token) para aprobar documentos
cypress/auth-frontend-adminPruebas end-to-end

Convenciones de build ​

vite.config.js define los alias @ (src/), @ui (src/components/ui/) y, en todos salvo el portal de postulantes, @pages (src/components/pages/, reservado; la carpeta no existe actualmente). La salida del build se organiza en js/, assets/img/ y assets/font/ con hash en el nombre.

Módulos por proyecto ​

Carpetas de src/views/ de cada frontend:

FrontendAPI que consumeMódulos (views/)
auth-authorization-frontendREST auth/flow/* + main-schemalogin-identifier, login-password, login-totp, login-register, login-recovery, login-consent, login-complete, auth-callback, logout
auth-frontend-adminadmin-schema (IDP)home, identidad-usuarios, interoperabilidad, configuracion
frontend-public-authpublic-schema (IDP)perfil, aprobacion-documentos, aprobacion-detalle, aprobacion-cd, federated
frontend-portal-admin-seleccion-postulantesadmin-schema (SPS)home, login, login-callback, convocatorias, convocatoria, convocatoria-historica, postulacion, postulacion-historica, historial-postulaciones, informes-previos, planillas, sgc, usuarios, accesos, custodio, interoperabilidad, provincias-municipios, preguntas-frecuentes, casos-ayuda
frontend-portal-comisiones-seleccion-postulantescomision-schemahome, login, login-callback, convocatorias, comision, sesion
frontend-portal-postulantes-seleccion-postulantespostulante-schemahome, login-callback, dashboard, convocatorias-vigentes, convocatoria-detalle, postulacion-proceso, aviso-detalle, perfil, verificacion, preguntas-frecuentes
frontend-estacion-examenestacion-examen-schemahome

Las APIs se describen en APIs de los backends.

Frontends: dependencias y configuración ​

Dependencias comunes ​

Presentes en los siete frontends, con la misma versión.

PaqueteVersiónUso
vue3.5.32Framework de interfaz
vue-router5.0.4Enrutamiento
pinia3.0.4Estado global
@vueuse/core14.3.0Composables utilitarios
tailwindcss4.3.0Estilos por clases utilitarias
@tailwindcss/vite4.3.0Integración de Tailwind con Vite
reka-ui2.9.8Componentes accesibles sin estilo (base de shadcn-vue)
class-variance-authority0.7.1Variantes de estilos de componentes
clsx · tailwind-merge2.1.1 · 3.6.0Combinación de clases CSS
@lucide/vue1.16.0Iconos

Dependencias por función ​

PaqueteVersiónUsoProyectos
@apollo/client · @vue/apollo-composable · @vue/apollo-util3.11.8 · 4.2.1 · 4.2.2Cliente GraphQLTodos excepto auth-authorization-frontend
graphql · graphql-tag16.12.0 · 2.12.6Definición de operaciones GraphQLÍdem
apollo-upload-client18.0.1Subida de archivos por GraphQLÍdem
date-fns4.3.0Manejo de fechasÍdem
vee-validate · @vee-validate/zod · zod4.15.1 · 4.15.1 · 4.4.3Validación de formulariosTodos excepto auth-authorization-frontend y la estación
@embedpdf/* (core, engines y plugins)2.14.4Visor de PDF embebidofrontend-public-auth y los tres portales del SPS
js-sha10.7.0Hash SHA-1 en el clientefrontend-public-auth y los tres portales del SPS
@tiptap/* (vue-3, starter-kit, pm y extensiones)3.26–3.27Editor de texto enriquecidoLos tres portales del SPS
@tiptap/extension-mention3.27.1Menciones en el editorPortales administrativo y de comisiones
tiptap-pagination-plus3.1.0Paginación en el editorPortal administrativo
echarts6.1.0GráficosPortales administrativo y de comisiones

auth-authorization-frontend no usa GraphQL de forma general: consume los endpoints REST del flujo de login con Fetch.

Herramientas de desarrollo ​

PaqueteVersiónUso
vite^8.0.8Servidor de desarrollo y empaquetado
@vitejs/plugin-vue^6.0.6Soporte de componentes .vue
vite-plugin-vue-devtools^8.1.1Vue DevTools integradas en desarrollo
eslint · @eslint/js · eslint-plugin-vue · globals^10.2.1 · ^10.0.1 · ~10.8.0 · ^17.5.0Linter
oxlint · eslint-plugin-oxlint~1.60.0Linter rápido (se ejecuta antes de ESLint)
prettier · eslint-config-prettier3.8.3 · ^10.1.8Formato de código
npm-run-all2^8.0.4Ejecución de scripts lint:* en serie
tw-animate-css^1.4.0Animaciones para Tailwind
@types/node^25.9.1Tipos de Node para el editor
cypress · eslint-plugin-cypress · start-server-and-test^15.16.0 · ^6.3.1 · ^3.0.5Pruebas end-to-end (solo auth-frontend-admin)

Componentes de interfaz (shadcn-vue) ​

Los componentes de src/components/ui/ siguen shadcn-vue: su código fuente vive en el repositorio (no es una librería instalada) y se construye sobre Reka UI y Tailwind. La configuración está en components.json (estilo new-york, iconos lucide, CSS en src/assets/css/tailwind.css).

Variables de entorno ​

Las variables VITE_* se definen en .env (plantilla .env.example) y se incrustan en el bundle al compilar. La lista por proyecto está en Imágenes Docker.

VariableDescripción
VITE_TITLE_APPTítulo de la aplicación
VITE_API_ENDPOINTURL base del backend
VITE_API_ENDPOINT_PATHPath del schema GraphQL (por ejemplo /admin-schema/)
VITE_API_ENDPOINT_FILESURL base de archivos públicos
VITE_API_FILES_PRIVATEURL base de archivos privados
VITE_AUTH_ORIGIN_PORTALPortal de origen enviado al iniciar sesión (por ejemplo PORTAL_ADMINISTRATIVO)
VITE_AUTH_PUBLIC_URLURL del portal de cuenta del IDP ("Mi cuenta")
VITE_LOGIN_REDIRECT_URLURL del portal de login del IDP (el logout usa <url>/logout)
VITE_PUBLIC_AUTH_PORTAL · VITE_ADMIN_AUTH_PORTALURLs de los portales del IDP
VITE_DOMAIN_PUBLIC_URLSe recibe en el build; sin uso en el código actual
VITE_JACOBITUS_API_URLURL del servicio Jacobitus (firma digital con token)
VITE_MANUAL_TECNICO_URL · VITE_MANUAL_USUARIO_URLEnlaces a los manuales

Scripts y ejecución local ​

sh
corepack enable pnpm
pnpm install
cp .env.example .env   # completar valores
pnpm run dev
ScriptAcción
pnpm run devServidor de desarrollo
pnpm run buildBuild de producción en dist/
pnpm run previewSirve el build localmente
pnpm run lintOxlint + ESLint con corrección automática
pnpm run formatPrettier sobre src/
pnpm run test:e2ePruebas Cypress (solo auth-frontend-admin)

Puertos y host local de desarrollo (package.json y server.allowedHosts de vite.config.js):

FrontendPuertoHost local
auth-authorization-frontend8080login-auth-eje-local.app.com.bo
auth-frontend-admin7070admin-auth-eje-local.app.com.bo, auth-admin-eje-local.app.com.bo
frontend-public-auth9092auth-public-eje-local.app.com.bo
frontend-portal-admin-seleccion-postulantes9090admin-portal-seleccion-eje-local.app.com.bo
frontend-portal-postulantes-seleccion-postulantes9091postulantes-portal-seleccion-eje-local.app.com.bo
frontend-portal-comisiones-seleccion-postulantes9093comisiones-portal-seleccion-eje-local.app.com.bo
frontend-estacion-examen9094estacion-examen-eje-local.app.com.bo

Manuales (VitePress) ​

Aplica a frontend-manual-tecnico y frontend-manual-de-usuario. Ambos son sitios estáticos generados con VitePress a partir de archivos Markdown.

Estructura ​

text
<manual>/
├── .vitepress/
│   ├── config.mjs         # Configuración del sitio: título, búsqueda, nav, sidebar
│   ├── rutas/ | sidebars/ # Sidebar de cada sección (un archivo .js por sección)
│   └── theme/             # Tema: extiende el tema por defecto y agrega CSS (pantalla e impresión)
├── docs/                  # Contenido (srcDir)
│   ├── index.md           # Página de inicio
│   ├── <seccion>/         # Una carpeta por sección: index.md + páginas numeradas
│   ├── consolidado/       # Páginas que reúnen secciones para exportar a PDF
│   └── public/            # Archivos servidos tal cual (logo, imágenes)
├── Dockerfile · nginx.conf
└── package.json · pnpm-lock.yaml · pnpm-workspace.yaml
ManualCarpeta de sidebars
frontend-manual-tecnico.vitepress/rutas/
frontend-manual-de-usuario.vitepress/sidebars/

Dependencias ​

PaqueteVersiónUso
vitepress2.0.0-alpha.17Generador del sitio
mermaid^12.2.0Diagramas en bloques ```mermaid
vitepress-plugin-mermaid^2.0.17Integración de Mermaid con VitePress (withMermaid en config.mjs)

Agregar una sección (manual técnico) ​

  1. Crear docs/<seccion>/index.md y las páginas numeradas (01-<tema>.md, 02-<tema>.md, …).

  2. Crear .vitepress/rutas/<seccion>.js exportando el sidebar:

    js
    const PATH_BASE = '/<seccion>';
    
    export const RUTAS_<SECCION> = [
      {
        link: `${PATH_BASE}/`,
        text: 'Título de la sección',
        items: [{ text: 'Página 1', link: `${PATH_BASE}/01-<tema>` }],
      },
    ];
  3. Importarlo en .vitepress/config.mjs y registrarlo en themeConfig.sidebar con la clave '/<seccion>/'.

  4. Agregar la tarjeta en features de docs/index.md.

  5. Incluir las páginas en la exportación de docs/consolidado/.

Ejecución local ​

sh
corepack enable pnpm
pnpm install
pnpm run docs:dev      # servidor de desarrollo
pnpm run docs:build    # genera .vitepress/dist
pnpm run docs:preview  # sirve el build

VitePress valida los enlaces internos durante docs:build: un enlace roto hace fallar el build (y el pipeline).

Custodio ​

Aplicación de escritorio portable que usa la comisión de elaboración para validar, consolidar y cifrar los bancos de preguntas antes de entregarlos al sistema.

Flujo ​

  1. Cada área entrega un banco .xlsx con las hojas Preguntas, Respuestas y _metadata (generadas por el sistema).
  2. Custodio valida que cada banco esté completo: todas las preguntas habilitadas con sus 4 incisos, respuesta correcta y justificación.
  3. Consolida los bancos válidos en Preguntas.xlsx y Respuestas.xlsx (una hoja por área).
  4. Cifra ambos archivos con la clave pública key.pub. Solo el sistema, que tiene la clave privada, puede descifrarlos.

El ejecutable se ubica en la misma carpeta que key.pub y la carpeta de bancos. Las versiones distribuidas se registran en el portal administrativo del SPS (app custodio_integration).

Estructura ​

text
custodio/
├── custodio.py               # Interfaz gráfica (Tkinter)
├── custodio_core.py          # Lógica de negocio sin interfaz (validación, consolidación, cifrado)
├── generar_claves_sistema.py # Utilidad de referencia: genera un par de claves X25519 (lado sistema)
├── tests/                    # Pruebas pytest de custodio_core
├── Custodio.spec             # Configuración de PyInstaller
└── requirements.txt

La separación entre custodio.py y custodio_core.py permite probar la lógica en CI sin entorno gráfico.

Dependencias ​

PaqueteVersiónUso
PyNaCl1.6.2Cifrado con clave pública (X25519)
openpyxl3.1.5Lectura y escritura de los bancos Excel
Pillow11.0.0Redimensionar las imágenes incluidas en las preguntas al consolidar
pyinstaller6.22.0Empaquetado como ejecutable portable
pytest9.1.1Pruebas

Requiere Python 3.12 con Tkinter.

Ejecución y build ​

sh
pip install -r requirements.txt

# Ejecutar en desarrollo
python custodio.py

# Pruebas
python -m pytest tests/ -v

# Ejecutable (dist/Custodio o dist/Custodio.exe)
pyinstaller Custodio.spec --clean

PyInstaller no hace compilación cruzada: el ejecutable de Windows se genera en Windows. El pipeline de publicación se describe en Pipelines CI/CD.