Convoca SDK¶
Guía para desarrollar extensiones del ecosistema Convoca sin leer el código.
Arquitectura¶
Convoca es una plataforma: core provee la infraestructura (registro de módulos, utilidades, licencias, logs, rate limiter) y los plugins (members, enroll, gateway, shifts, publisher, assistant) son módulos registrados. Las extensiones externas se registran igual.
┌─────────────────────────────────────────────┐
│ Tu extensión (plugin independiente) │
│ ┌───────────────────────────────────────┐ │
│ │ convoca_register_module (filtro) │ │
│ └──────────────┬────────────────────────┘ │
└─────────────────┼───────────────────────────┘
▼
┌─────────────────────────────────────────────┐
│ Convoca Core (Module_Registry) │
│ - valida core_min / features / capabilities│
│ - llama al bootstrap │
│ - expone Utils, Logger, License_Manager │
└─────────────────────────────────────────────┘
Registro de módulo¶
// En tu plugin principal:
add_filter( 'convoca_register_module', 'mi_ext_register' );
function mi_ext_register() {
if ( ! class_exists( 'Convoca\\Core\\Module_Registry' ) ) {
return array(); // core no activo — funcionas como plugin normal
}
return array(
'mi-extension' => array(
'name' => __( 'Mi Extensión', 'mi-extension' ),
'version' => '1.0.0',
'core_min' => '2.1.0',
'features' => array(), // features PRO requeridas
'capabilities' => array(), // capacidades requeridas
'bootstrap' => 'mi_ext_bootstrap',
),
);
}
El core valida el contrato y llama bootstrap solo si es compatible.
API disponible para extensiones¶
| Recurso | Acceso |
|---|---|
\Convoca\Core\Utils:: |
34 helpers: DNI, fechas, rate limit, locks, notices, PDF |
\Convoca\Core\Features:: |
detectar plugins activos (is_members_active(), etc.) |
\Convoca\Core\Logger:: |
logging unificado |
\Convoca\Core\License_Manager:: |
licencias y features PRO |
\Convoca\Core\Module_Registry:: |
registro de módulos |
Contratos entre plugins: el vocabulario y las personas¶
Regla del ecosistema, y la que más se incumple al escribir una extensión: no averigües quién es quién.
| Qué | De quién es | Cómo se usa |
|---|---|---|
Roles y capacidades (socio, voluntario_aprobado, monitor_actividad) |
Convoca Core | Convoca\Core\Capabilities. No los crees: add_role() no pisa un rol existente, así que quien llega segundo no añade nada y el rol se queda a medias según quién activara primero. |
| Quién es voluntario, quién está aprobado, quién es monitor | Convoca Members | Pregunta por el contrato, no por el rol |
| Quién se asigna a un turno | Shifts, con las personas que le den | convoca_shifts_personas_asignables |
| Quién acredita horas | El libro de horas de Core | convoca_can_credit_hours |
convoca_shifts_personas_asignables¶
/**
* @param array<int, array{id:int, nombre:string}> $personas Lista; por defecto VACÍA.
* @param string $contexto 'voluntario' | 'monitor'
*/
$personas = apply_filters( 'convoca_shifts_personas_asignables', array(), $contexto );
Si eres el dueño de las personas, contesta con array( array( 'id' => 12, 'nombre' => 'Ana' ), … ).
Si no lo eres, no contestes: el valor por defecto vacío es el correcto, y significa «aquí no hay a quién
asignar». Shifts pinta la pantalla con la nota del motivo y no deja asignar.
convoca_can_credit_hours¶
/**
* @param bool $puede Por defecto FALSE.
* @param int $user_id Usuario de WordPress.
*/
$puede = apply_filters( 'convoca_can_credit_hours', false, $user_id );
El valor por defecto es no a propósito: sin gestión de personas no se acreditan horas de voluntariado ni de monitorado. El turno se marca realizado igual; lo que no hay son horas.
convoca_member_identity y convoca_member_priority¶
Quién es el socio con la sesión abierta, y si su inscripción tiene prioridad:
/**
* @param array{autenticado: bool, member_id: int, datos: array} $identidad Por defecto, nadie.
*/
$identidad = apply_filters( 'convoca_member_identity', array(
'autenticado' => false,
'member_id' => 0,
'datos' => array(),
) );
/**
* @param bool $prioridad Por defecto SÍ (sin gestión de socios no hay cuota que verificar).
* @param int $inscripcion_id Inscripción.
*/
$prioridad = apply_filters( 'convoca_member_priority', true, $inscripcion_id );
Se preguntan a través de Convoca\Core\Member_Identity, que guarda la respuesta sólo durante la
petición: la sesión del socio vive en un transient y caduca. Tras un login o un logout en la misma carga,
quien contesta llama a Member_Identity::olvidar().
Ojo con la asimetría del defecto: la identidad falla en «nadie» (seguro) y la prioridad falla en «sí» (porque es lo que hacía el código antes de existir el contrato). No es incoherencia: es no cambiar el comportamiento de nadie al refactorizar.
Por qué el defecto es «no»¶
Los dos contratos fallan cerrados. Si nadie contesta, el sistema no inventa personas ni horas. Antes lo hacía: cada plugin leía roles por su cuenta y, en una instalación sin Convoca Members, aparecían personas asignables y se acreditaban horas que nadie podía justificar.
Patrones recomendados¶
CPT¶
add_action( 'init', function () {
register_post_type( 'mi_cpt', array(
'label' => __( 'Mi CPT', 'mi-extension' ),
'public' => true,
'supports' => array( 'title', 'editor' ),
) );
} );
Shortcode¶
add_shortcode( 'mi_shortcode', function ( $atts ) {
$atts = shortcode_atts( array( 'x' => '' ), $atts );
return '<div>' . esc_html( $atts['x'] ) . '</div>';
} );
REST endpoint¶
add_action( 'rest_api_init', function () {
register_rest_route( 'mi-extension/v1', '/dato', array(
'methods' => 'GET',
'permission_callback' => '__return_true',
'callback' => function () {
return rest_ensure_response( array( 'ok' => true ) );
},
) );
} );
Hook público¶
Plugin de ejemplo¶
Hello Convoca — plantilla completa y funcional:
- Repo: github.com/josecarlosnieto91/hello-convoca
- Demuestra: registro de módulo, CPT, shortcode, REST, hook, admin
- Verificado funcionando contra core real
Convenciones¶
- Prefijos:
mi_extension_*en funciones,mi-extension/en namespaces REST - Namespaces:
register_rest_route( 'mi-extension/v1', ... ) - Escaping: nunca imprimir sin
esc_html()/esc_attr() - Sanitización:
sanitize_text_field()en entradas - Nonces:
wp_verify_nonce()en POST admin - Versiones: sincronizar header, constante y Stable tag
API congelada¶
La API pública v3.0 está congelada — los hooks, REST endpoints y
shortcodes no cambian sin deprecación. El CI verifica la congelación en
cada push. Referencia: docs/openapi.yaml (OpenAPI) y
docs/hooks-inventario.md del repo convoca-health-check.