Saltar a contenido

Convoca Members

Guía para administradores: gestión de socios, membresías, cuotas, voluntariado y ciclo de vida completo del socio. Documentación fiel al código de Convoca Members 2.8.23.


Manual para la junta directiva (sin conocimientos técnicos)

Esta página es la referencia técnica del plugin. Para el día a día hay un manual en tres partes, en castellano y con capturas:

1. Introducción

Convoca Members gestiona el ciclo de vida completo de los socios de la asociación: altas, renovaciones, periodos de gracia, bajas automáticas, cuotas, estados de membresía, horas de voluntariado, carnets, certificados y documentación. Ideal para asociaciones, ONGs y entidades sin ánimo de lucro.

Requisitos: convoca-core activo, WordPress 6.4+, PHP 8.1+. Si no está activo convoca-core, el plugin muestra un aviso en el administrador y no se carga.

2. Estructura de datos

Convoca Members registra estos Custom Post Types (todos privados, sin show_in_rest) y una taxonomía:

Tipo Slug Uso
CPT miembro miembro Ficha del socio (todos los datos en meta _convoca_*)
CPT convoca_documento convoca_documento Documentos generados (p. ej. acuerdo de incorporación); sin interfaz propia
CPT registro_hora registro_hora Registros de horas de voluntariado
CPT proyecto proyecto Proyectos de voluntariado (desplegable en el registro de horas)
Taxonomía tipo_miembro tipo_miembro Clasificación adicional (heredada del libro de socios)

Los datos personales de miembro no se exponen por el REST nativo de WordPress (show_in_rest => false); solo se leen a través de las rutas propias autenticadas (ver §17).

3. Configuración inicial

Accede a Miembros → Ajustes (slug conv-members-settings; requiere manage_options). La pantalla tiene estas pestañas:

Pestaña Contenido
General Email administrador, remitente, IBAN, proveedor de email, RGPD, edad mínima, ciclo de vida de la membresía y tema de documentos
Planes de Socio Editar/añadir planes (precio, horas, modalidad, ventajas)
Plantillas de Email Editar asunto y cuerpo de las plantillas
Campos Voluntariado Texto legal y campos personalizados del formulario de voluntariado
Gamificación Badges por modalidad (solo licencia PRO)
Estado Diagnóstico del sistema (plugins, páginas con shortcode, versión de BD)

El ajuste convoca_volunteer_legal_text (pestaña Campos Voluntariado) guarda la Declaración Responsable: lo que la persona declara y acepta al darse de alta como voluntaria. Es un solo texto y aparece en los dos sitios a la vez, así que no hay que mantener dos copias:

  • el formulario público de voluntariado, en el recuadro gris que va justo encima de la casilla «He leído y acepto la Declaración Responsable» (obligatoria);
  • el acuerdo de incorporación en PDF, donde se imprime bajo el epígrafe «Declaración Responsable», antes del sello de aceptación digital.

Admite HTML sencillo (p, strong, ul). Dos cosas que hay que respetar al editarlo:

  • Sin emojis: el PDF se dibuja con una tipografía que no tiene sus glifos y saldrían como «?».
  • Corto: el acuerdo tiene que caber en una página A4. El texto de 2026 cabe (verificado en de ejemplo y en la demo: 1 página, 595,28 × 841,89 pt); si se alarga, se irá a dos.

El texto vigente (fuente: Programa de Voluntariado 2026-2030 v2, Manual del Voluntariado 2026, Reglamento Interno 2026 y normativa de referencia):

<p>Declaro que los datos que facilito son ciertos y que reúno los requisitos para ser voluntario/a de [Nombre de la asociación]: actúo de forma voluntaria, altruista y sin contraprestación económica (Ley 45/2015, de 14 de octubre, del Voluntariado) y no figuro en el Registro Central de Delincuentes Sexuales ni tengo antecedentes penales no cancelados. Si soy menor de edad, cuento con la autorización de mis representantes legales.</p>
<p>He leído y acepto el Programa de Voluntariado 2026-2030, el Manual del Voluntariado, el Código Ético y de Conducta y el Reglamento Interno, y me comprometo a cumplir las tareas que se me asignen, a asistir a las reuniones de planificación y a participar en la formación necesaria. Mantendré la confidencialidad de la información de las personas participantes, también después de finalizar mi colaboración, y sé que la asociación dispone de un seguro de accidentes y de responsabilidad civil para la acción voluntaria.</p>
<p>Mis datos se tratan para gestionar el voluntariado (alta, seguro, formación, acreditación y certificación). Responsable: [Nombre de la asociación] ([CIF]), Registro de Asociaciones de [territorio] n.º [número] y Registro de Entidades de Voluntariado n.º [número]. Base: mi consentimiento y la ejecución del acuerdo. No se ceden a terceros salvo obligación legal. Puedo ejercer mis derechos de acceso, rectificación, supresión y los demás escribiendo a [correo de protección de datos]; el detalle completo está en la Política de Privacidad. Puedo revocar esta declaración y darme de baja cuando quiera, sin tener que justificar el motivo.</p>

La baja es libre: la propia declaración dice que se puede revocar sin justificar el motivo, en línea con el artículo 9 de la Ley 45/2015.

Ajustes generales

Ajuste Clave Descripción
Email administrador convoca_members_settings[admin_email] Recibe notificaciones de nuevas altas
Nombre remitente convoca_members_settings[sender_name] Nombre en el From: de los emails
IBAN convoca_members_settings[iban] Para transferencias
Proveedor de email convoca_members_settings[email_provider] wpmail (defecto) o mailgun (API key, dominio y región US/EU)
Versión texto legal RGPD convoca_members_settings[rgpd_version] Por defecto 1.0
Edad mínima convoca_members_settings[min_age] 0 desactiva la restricción

Ajustes de Convoca Members (General)

4. Planes de membresía

Los planes se definen en la opción convoca_members_plans. Si está vacía, se usan los valores por defecto definidos en CPT_Miembro::DEFAULT_PLANS y se pueden personalizar en Ajustes → Planes de Socio.

Planes por defecto (precio € / horas anuales de voluntariado)

Modalidad Plan Precio Horas Clave
Numerario 🥉 Bronce 30 € 15 h bronze
Numerario 🥈 Plata 50 € 25 h silver
Numerario 🥇 Oro 100 € 50 h gold
Familiar Familiar Bronce 45 € 22,5 h fam-bronze
Familiar Familiar Plata 75 € 37,5 h fam-silver
Familiar Familiar Oro 150 € 75 h fam-gold
Juvenil Juvenil Bronce 15 € 7,5 h juv-bronze
Juvenil Juvenil Plata 25 € 12,5 h juv-silver
Juvenil Juvenil Oro 50 € 25 h juv-gold

El objetivo de horas es configurable por plan desde Ajustes → Planes de Socio (campo «Horas Voluntariado»). Los valores de la tabla son los defecto: bronze = 15 h, silver = 25 h, gold = 50 h.

Selectores de modalidad

Clave Modalidad Uso
familiar Virtual Selector "Familiar" (precio 0): el formulario pide además un sub_plan (fam-bronze, fam-silver o fam-gold)
juvenil Virtual Selector "Juvenil" (precio 0): el formulario pide además un sub_plan (juv-*)

Cuando el socio elige el selector familiar o juvenil, la lógica resuelve el plan real desde sub_plan. Los planes aceptan además payment_methods (bizum, tarjeta, transferencia), advantages (una por línea), active, order, emoji y description. Se pueden filtrar por código con convoca_members_plans.

Validaciones en el alta: la modalidad Juvenil exige tener menos de 30 años; un plan desactivado (active = false) se rechaza.

5. Alta de socio

Desde el formulario público

Coloca el shortcode en una página pública:

[convoca_alta_socio]

El formulario (multi-paso, plantilla templates/form-alta.php) recoge: nombre, DNI/NIE (validado con suma de control), email, teléfono, fecha de nacimiento, dirección, municipio, canal de contacto, plan/modalidad, forma de pago, consentimiento RGPD y comunicaciones, y (opcional) pago recurrente. Si el socio es menor de edad se piden los datos del tutor.

El envío se hace por REST POST /convoca-members/v1/alta (con nonce convoca_alta_nonce) o por el AJAX heredado. Está limitado a 3 registros por hora por IP.

El estado inicial depende de la forma de pago:

Forma de pago Estado inicial
cuota / tarjeta / bizum / transferencia pendiente_pago
voluntariado pendiente_documentacion

Si el plan tiene importe y el gateway está activo, el alta redirige al TPV. Si hay duplicado de DNI o email, el sistema lo detecta atómicamente (ver §7, re-alta).

Desde el panel de administración

Miembros → Añadir nuevo (o Añadir voluntario) abre el editor de socio. La aprobación de un socio (botón Aprobar en el listado o vía pago completado) ejecuta CPT_Miembro::approve_member, que:

  1. Asigna el número de socio secuencial (tabla convoca_member_sequence).
  2. Establece fecha_alta (solo la primera vez) y fecha_renovacion (+1 año si no existía).
  3. Pasa el estado a activo y crea el usuario de WordPress vinculado, enviando credenciales por email (Process_Member::handle_approved).

6. Estados de membresía

Cada socio tiene un estado guardado en _convoca_estado_miembro. La máquina de estados (Estados) valida cada transición y registra el historial en _convoca_historial y en el log de auditoría.

Estado Significado
Pendiente documentación (pendiente_documentacion) Alta de voluntariado (o recién registrado) a la espera de cumplir requisitos
Pendiente pago (pendiente_pago) Alta con cuota sin pagar
Activo (activo) Membresía vigente
Suspendido (suspendido) Periodo de gracia: pierde beneficios, solo puede renovar
Baja solicitada (baja_solicitada) El socio pidió la baja desde su panel; pendiente de confirmar
Baja (baja) Membresía finalizada (conservada para histórico)

Transiciones permitidas

Desde Puede pasar a
pendiente_documentacion pendiente_pago, activo, baja
pendiente_pago activo, suspendido, baja
activo suspendido, baja_solicitada, baja, pendiente_documentacion
suspendido activo, baja_solicitada, baja
baja_solicitada baja, activo
baja pendiente_documentacion, pendiente_pago, activo (re-alta/reactivación)

Cambiar estado manualmente

En la ficha del socio (detalle ?member_id=X) el desplegable Cambiar estado solo ofrece los destinos permitidos según el estado actual; el cambio se aplica por AJAX con su nota de auditoría. En el listado también hay acciones rápidas de aprobar y eliminar (papelera).

7. Ciclo de vida de la membresía

El plugin aplica automáticamente un ciclo de vida configurable tras la fecha de renovación (_convoca_fecha_renovacion). Los parámetros se ajustan en Ajustes → General → Ciclo de vida de la membresía:

Ajuste Clave Rango Defecto Efecto
Días hasta perder beneficios grace_suspend_days 0–30 1 Días tras el vencimiento antes de pasar a suspendido
Días hasta la baja automática grace_baja_days 1–90 30 Días tras el vencimiento antes de pasar a baja

La evaluación se hace en CPT_Miembro::check_member_status, invocada por el cron diario (Cron_Manager::process_expirations) para todo socio activo con renovación vencida:

  1. Suspensión (gracia corta): al cumplirse grace_suspend_days desde el vencimiento (con defecto 1, el día siguiente al vencimiento), el socio pasa a suspendido, _convoca_estado_cuota se marca vencida. Pierde los beneficios pero conserva el acceso al panel solo para renovar.
  2. Baja automática (gracia larga): al cumplirse grace_baja_days, antes de cambiar el estado se cierran todas sus sesiones (Member_Auth::logout_member_sessions), pasa a baja, se guarda fecha_baja y se dispara el hook convoca_members_miembro_dado_de_baja.

Nota: el orden importa. La baja se evalúa primero (es la condición más severa); la suspensión solo se aplica si aún no ha llegado la fecha de baja.

Renovación tardía (siempre desde el vencimiento)

Cuando un socio en activo, suspendido o baja_solicitada paga la cuota, Payment_Listener suma siempre 1 año a la fecha de vencimiento anterior (_convoca_fecha_renovacion + 1 año), nunca desde "hoy". Así una renovación tardía no pierde el periodo ya pagado. La primera activación (sin fecha previa) o la re-entrada tras baja sí arrancan un ciclo nuevo desde hoy.

Re-alta tras baja (sin duplicar)

Si un socio dado de baja vuelve a rellenar el formulario con el mismo DNI o email, Process_Member::register detecta la ficha existente en estado baja y reactiva la misma ficha: conserva el número de socio, la antigüedad (fecha_alta histórica) y el historial; actualiza plan, forma de pago y datos de contacto. No se crea un registro duplicado.

Ciclo anual de voluntarios (por horas)

Los socios con forma_pago = voluntariado no se rigen por fechas de cuota. CPT_Miembro::check_volunteer_cycle evalúa, al vencer su renovación, si completaron las horas anuales del plan:

  • Si las horas aprobadas del ciclo ≥ objetivo → renueva fecha_renovacion + 1 año y sigue activo.
  • Si no cumplen → vuelve a pendiente_documentacion (solo voluntario, sin beneficios de socio) hasta completar de nuevo las horas.

8. Renovaciones

Renovación desde el panel del socio

  • Botón "Renovar membresía" (pestaña Pagos y Cuotas) y el shortcode [convoca_renovar] (página /renovar/) llaman a POST /me/renovar, que crea un pago por un año y devuelve payment_url para redirigir al TPV.
  • La renovación está permitida para socios activo y suspendido (check_renewable_member). Un socio baja no puede renovar desde el panel: debe re-darse de alta (re-alta, §7).

Renovación automática (recurrente)

Los socios con _convoca_pago_recurrente = 1 se renuevan automáticamente por cron (process_automatic_renewals): se genera el cargo con el gateway (cargo real por token vía Redsys REST) y se envía renovacion_automatica.

Política de reintentos y gracia (D1/D2): si el cargo falla, el socio conserva los beneficios mientras haya reintentos activos: se reintenta cada ciclo diario hasta auto_renew_max_attempts (opción de Ajustes → Ciclo de vida, 3 por defecto) dentro de una ventana de auto_renew_retry_days (5 días). Cada intento fallido incrementa _convoca_autorenew_attempts y registra _convoca_autorenew_last_attempt; mientras haya reintentos pendientes, check_member_status no suspende ni da de baja al socio. Agotados los intentos (o sin token guardado), pasa a pendiente_pago y recibe un enlace de pago manual con solicitud de tokenización para futuras renovaciones.

Renovación por horas de voluntariado (recordatorios)

Los recordatorios escalados de renovación (30, 15 y 7 días antes del vencimiento; plantillas renovacion, renovacion_15d y renovacion_7d) se envían a todos los socios activos con fecha de renovación, incluidos los que renuevan por horas de voluntariado (forma_pago = voluntariado). Para estos, las plantillas incluyen además:

Variable Valor
{horas_actuales} Horas aprobadas acumuladas
{horas_objetivo} Objetivo de horas del plan
{porcentaje_cumplimiento} Porcentaje de cumplimiento

El objetivo de horas es configurable por plan (ver §4).

9. Bajas

Las bajas son lógicas: el registro se conserva para histórico. Pueden producirse por tres vías:

  1. Automática por impago al agotarse grace_baja_days (§7), con cierre de sesión.
  2. Solicitada por el socio desde su panel (POST /me/unsubscribe → estado baja_solicitada); un administrador confirma después el baja.
  3. Manual desde la ficha del socio (transición a baja, estableciendo fecha_baja).

⚠️ No elimines un socio salvo obligación legal: el sistema conserva todo el histórico. La eliminación (papelera) es una acción aparte disponible en el listado.

10. Pagos y cuotas

Convoca Members se integra con Convoca Gateway. Payment_Listener escucha convoca_gateway_payment_completed / convoca_gateway_payment_failed y gestiona el estado del socio:

  • Pago completado: actualiza estado_cuota, metodo_pago, calcula la nueva fecha_renovacion (§7), reactiva/activa el socio y envía renovación completada (con el carnet adjunto).
  • Pago fallido: marca estado_cuota como vencida (o pendiente en el primer intento) y reenvía un enlace de pago.

El historial de pagos del socio se lee desde el CPT pago del gateway (origen members), visible en la pestaña Pagos y Cuotas del panel y en GET /me/pagos.

11. Panel del socio (área personal)

El shortcode [convoca_mi_area] (página Mi Cuenta) muestra el panel del socio autenticado. La autenticación usa las credenciales de WordPress (usuario o email + contraseña) recibidas por email al aprobarse el alta, y una sesión propia en la cookie convoca_member_session (6 horas).

Pestaña Funcionalidad
Mis Datos Ver y editar dirección, teléfono, email y cumpleaños; solicitar la baja
Carnet Digital Carnet de socio (HTML imprimible)
Inscripciones Actividades activas y pasadas (vía convoca-enroll)
Voluntariado Registrar y consultar horas
Pagos y Cuotas Historial de pagos + botón Renovar membresía
Buscar Búsqueda de actividades (y socios si hay sesión)
Notificaciones Avisos internos con contador de no leídos

Un socio suspendido sigue accediendo al panel pero solo en lectura y para renovar (no puede editar perfil ni registrar horas nuevas).

Reglas de edición de datos (perfil)

Dato Regla
Dirección Editable libremente
Teléfono Editable; verificable con enlace enviado al email
Email Doble opt-in: el cambio queda pendiente hasta confirmar el enlace (caduca a las 24 h)
Cumpleaños Solo se puede establecer una vez; después no se modifica (se rechaza con error)

Verificación de teléfono y email

  • Teléfono: botón Verificar teléfono → POST /me/verify-phone genera un enlace de 24 h enviado al email del socio; al pulsarlo (?convoca_confirm_phone=1&member=X&token=Y) se marca telefono_verificado = 1.
  • Email: al cambiar el email, POST /me/profile guarda email_pendiente + token y envía el enlace de confirmación; POST /me/confirm-email (o el enlace ?convoca_confirm_email=1…) aplica el cambio.

12. Carnet digital

El carnet se sirve como HTML impreso (PDF_Card::get_html) con botón "IMPRIMIR / GUARDAR PDF", y como PDF (PDF_Card::generate_pdf) vía Convoca\Core\Signature (Dompdf).

Característica Detalle
Tema Claro u oscuro según la opción compartida del core convoca_document_theme (Ajustes → General)
QR Generado localmente con chillerlan/php-qrcode (sin API externa), apunta a la verificación pública del socio
Paleta de marca Púrpura #320028 + naranja #ff8700
Contenido Nº de socio (4 dígitos), nombre, plan (badge), insignia de antigüedad, fecha de alta y dominio

La insignia de antigüedad (D3) muestra los años continuados desde la fecha de alta (_convoca_fecha_alta, conservada en las re-activaciones): aparece «N año(s)» junto al plan a partir de 1 año de pertenencia y queda oculta antes de cumplirse el primer año. Es un reconocimiento visual; no conlleva descuentos automáticos.

El carnet también se adjunta como PDF al email de renovación completada.

El PDF es la tarjeta, no un folio con la tarjeta dentro. La página mide lo que la tarjeta (450×280 px = 119×74 mm) y no lleva el botón de imprimir: Dompdf ignora @media print, así que lo marcado como no-print —el botón y su texto de ayuda— acababa dentro del PDF. El HTML que ve el navegador sí lo conserva.

Tres cosas del motor de PDF que costaron una página de más y quedan resueltas en el modo PDF (get_html($id, $theme, true)): Dompdf no respeta box-sizing: border-box (suma el padding y el borde a las medidas, así que a la tarjeta se le iban 60 px de más y no cabía), no sabe hacer flexbox (las tres zonas se apilaban y el QR se salía) y cuenta los adornos decorativos que sobresalen del folio. En PDF se dan las medidas del contenido y las zonas se colocan con flotantes y clear. Además, un emoji en la etiqueta del plan no tiene glifo en la tipografía del PDF y salía como «?»: en el PDF se quita el emoji y se conserva el texto.

La generación usa get_temp_dir() del core y no wp_tempnam() (que vive en wp-admin y no está cargado fuera del escritorio: un correo de cron que adjuntase la tarjeta rompía).

Y no lleva overflow: hidden en la cabecera ni en el pie: Dompdf calcula esos contenedores con altura cero cuando tienen flotantes dentro y recorta su contenido, así que el carnet salía impreso con dos líneas y sin logo, insignias, fecha ni QR (aunque el texto siguiera en el fichero). El nombre ajusta su tamaño por longitud (20 / 16 / 13 px) para que un nombre largo no se cruce con el pie.

La modalidad del socio en el carnet

El distintivo naranja del carnet muestra la modalidad (el plan del socio). Se resuelve con CPT_Miembro::get_plan(), que:

  1. busca la clave exacta (lo normal: basico, familiar, bronze…);
  2. si no existe, busca por etiqueta del plan y por clave normalizada — sin adornos (los planes llevan emoji delante: la etiqueta de la demo es literalmente «🥉 Bronce»), sin acentos, sin distinguir mayúsculas y sin espacios de sobra.

Ese segundo paso hace falta porque hay socios guardados con la etiqueta en vez de la clave (los sembrados de la demo tienen «Bronce» donde debería ir bronze). Sin él, el plan no se resolvía y los tres documentos caían al texto genérico: el carnet salía con «SOCIO/A».

Verificado en vivo: en la demo, tres socios reales pasaron de «SOCIO/A» a BRONCE / PLATA / ORO, y en la asociación de ejemplo las modalidades basico y familiar resuelven por clave, por etiqueta, con adorno y en cualquier caja. Límite conocido: solo se compara con la clave o la etiqueta completas; un valor intermedio guardado en el socio (p. ej. «Familiar Plus» cuando el plan se llame «Modalidad Familiar Juvenil Plus») no se resuelve. En la instalación de ejemplo no hay ningún plan con ese nombre.

Si la etiqueta es larga (más de 15 caracteres) el distintivo no cabe en la línea del logo y, en el PDF, ocupaba la línea entera metiéndose encima del nombre de la organización. Montar la cabecera como tabla o con posiciones absolutas tampoco servía (las insignias caían en la línea de los datos, o el logo se partía en dos líneas). En el PDF, una modalidad larga ocupa su propia línea y el cuerpo y el pie bajan; en el navegador no hace falta porque lo reparte el flexbox. Las etiquetas cortas (las de uso habitual) se quedan exactamente como estaban.

Base de estilos común

Los tres documentos (carnet, acuerdo y certificado) comparten Estilos_PDF: la tipografía y los colores de la marca en un solo sitio (constantes de PHP, porque Dompdf no soporta variables CSS) y los resets neutros que tienen en común. La base se antepone a la hoja de cada documento, así que cada uno manda en lo suyo y añadirla no cambia lo que ya se veía — comprobado antes y después con los tres documentos generados: mismas páginas, mismo tamaño de folio y el mismo texto.

Y con la base van escritas las trampas de Dompdf, medidas una a una y documentadas al lado de las reglas. Son las que cuestan una tarde cada vez que se olvidan:

Regla Qué pasa en Dompdf
display: flex No existe: los hijos se apilan. Se coloca con float y clear.
overflow: hidden con flotantes dentro Recorta el contenido (calcula el contenedor con altura cero). El texto sigue en el fichero, así que pdftotext no lo delata: hay que mirar el PDF.
box-sizing: border-box No se respeta: suma el padding y el borde a las medidas (un bloque de 450×280 con padding de 30 px se va a 510×340).
@media print No existe: lo marcado como no-print acaba dentro del PDF.
Emojis La tipografía del PDF no tiene el glifo: sale un «?». Usar Texto_PDF::sin_emoji().
var(--x) No funciona: los colores van escritos (de ahí las constantes).
Cifras number_format_i18n() devuelve el formato inglés aunque el locale sea es_ES: usar Texto_PDF::horas() («18,5»).
Medir páginas pdfinfo , nunca file: file cuenta coincidencias de /Type /Page y cuela el /Type /Pages del árbol, así que da un número que no es (sobre un carnet de una página dijo «3 page(s)»). Y pdftotext -f 2 -l 2 sobre un PDF de una página no da error: devuelve vacío.

Carnet digital de socio

13. Certificados de voluntariado

Certificate_Generator emite el certificado cuando el socio ha completado las horas aprobadas ≥ objetivo del plan (recalculado al solicitarlo, no solo por meta).

  • Generación: GET /me/certificate devuelve certificado_id, horas y download_url; GET /me/certificate/download sirve el PDF.
  • Formato del ID: VOL-AAAA-XXXXX (año + 5 caracteres), p. ej. VOL-2025-A1B2C.
  • QR local (chillerlan) enlaza a la verificación pública.

De dónde salen las horas

Del libro de horas: el certificado suma _convoca_horas de los registros del socio (registro_hora con _convoca_member_id) cuyo estado es aprobada. Así, un registro anulado o rechazado no cuenta — verificado: 8 + 6 + 4,5 h aprobadas y una anulada de 10 h dan un certificado de 18,50 h.

Detalles de texto (medidos sobre el PDF)

  • El documento sale en una página A4, con el QR abajo a la derecha.
  • Las horas van con la convención del castellano: «18,5 horas» y «1.234,5» si algún día hay muchas. No se sigue el formato del idioma del sitio a propósito: medido en un sitio con locale es_ES y las traducciones cargadas, WordPress devolvía decimal_point = '.' y thousands_sep = ',' (el formato inglés), así que el certificado salía con las letras en castellano y las cifras en inglés. Un sitio que quiera otra convención tiene el filtro convoca_documento_horas.
  • La etiqueta del plan se imprime sin emojis: la tipografía del PDF no tiene el glifo y salía un «?» (los planes de la demo se llaman «🥉 Bronce»).

El flujo completo, y la misma cifra en la web

El recorrido entero es: el socio pide el certificado (GET /me/certificate) → generate() dibuja el PDF y estampa tres metas en el socio (_convoca_certificado_id, _convoca_certificado_emitido, _convoca_certificado_valido_hasta) → cualquiera verifica el folio en la página pública /verificar-certificado/, que es un formulario (?id= solo lo rellena; el resultado sale al enviarlo) → Certificate_Generator::verify() busca al socio por esa meta y devuelve nombre, horas, emisión, validez y folio, o nada. Verificado en vivo: con un folio real responde con el nombre y las horas, y con uno inventado dice «Certificado No Encontrado» (nunca un error).

Las cifras son las mismas en la web que en el PDF. La verificación pública imprimía las horas con number_format( $horas, 1 ), o sea con los separadores de PHP: «12.5 h». Y el importe de renovación del panel del socio daba «50.00 €» por lo mismo. Los dos van ya con la coma castellana (Texto_PDF::horas() y separadores explícitos). Ojo con esto al escribir pantallas nuevas: number_format() sin separadores no vale para algo que lee una persona; y number_format_i18n() tampoco, medido (el sitio «en castellano» devolvía el formato inglés). Sin decimales no hay problema: number_format( $x, 0 ) se queda como está.

Lo vigila FormatoCifrasTest, que recorre includes/, public/ y admin/ buscando cifras con decimales formateadas sin separadores explícitos; comprobado que caza el fallo (se metió a propósito).

Validez del certificado (1 año)

El certificado tiene una validez de 1 año desde la emisión, configurable con el filtro convoca_members_certificado_validez_anios. El PDF muestra «Válido hasta: (N año/s)» junto a la fecha de emisión.

  • Al aprobarse horas nuevas (hook convoca_members_hora_aprobada), la validez se regenera desde la fecha actual (el certificado cubre las horas completadas).
  • La validez se guarda en la meta _convoca_certificado_valido_hasta.
  • Retrocompatible: los certificados emitidos antes de esta política calculan su caducidad como emisión + 1 año.

Verificación pública

[convoca_verificar_certificado]

Introduciendo el ID se muestra nombre, horas, fecha de emisión y validez, y distingue el estado: vigente (verde ✅) o caducado (ámbar ⚠️ «Certificado Caducado»). No requiere sesión.

Verificación pública de certificado

14. Verificación pública de membresía

Los socios pueden verificar su estado sin iniciar sesión escaneando el QR del carnet:

[convoca_verificar_socio]

El QR apunta a /verificar-socio/?id=<ID>&token=<hash>. El shortcode valida el token (HMAC-SHA256 con sal persistente), comprueba que el socio existe y está activo, y muestra nombre, número de socio y plan.

15. Voluntariado

Alta de voluntario

[convoca_voluntariado]

Formulario de registro (plantilla templates/form-voluntariado.php) con texto legal obligatorio y campos personalizados configurados en Ajustes → Campos Voluntariado (opción convoca_volunteer_fields).

Acuerdo de incorporación (PDF)

PDF_Document::generate_volunteer_agreement($user_id) genera el acuerdo de incorporación y lo guarda como convoca_documento, con el PDF en uploads/convoca-documentos/ y un hash de firma. Es idempotente: si esa persona ya tiene un acuerdo publicado, devuelve ese y no crea otro.

Pieza Detalle
Plantilla Opción convoca_pdf_templates['acuerdo_incorporacion'], con marcadores {{nombre}}, {{dni}}, {{fecha_incorporacion}}, {{dynamic_fields}}, {{declaracion}}
Campos Nombre, DNI, fecha de incorporación, campos personalizados y la declaración responsable (texto legal de convoca_volunteer_legal_text)
Sello digital Aceptado por, fecha y hora, IP y hash SHA-256; sustituye al comentario marcador de la plantilla
Una página El documento entero cabe en una página; el sello se iba a la segunda y ahora el espaciado se aprieta lo justo

El marcador del sello se reconoce con el nombre antiguo de la clase (BDV_Signature) y con el actual (Signature): las plantillas guardadas antes decían el viejo y, al no reconocerlo, el sello caía siempre al final del documento en lugar de donde el sitio lo hubiera puesto. El correo de alta lo adjunta.

Registrar horas

El voluntario registra horas desde su panel (pestaña Voluntariado) vía POST /me/horas (solo socios activos). El registro se guarda en el CPT registro_hora con estado inicial pendiente.

Aprobación

Un administrador aprueba o rechaza desde la gestión de horas. Estados posibles: pendiente, aprobada, rechazada. Al aprobar (convoca_members_hora_aprobada), Voluntariado_Manager suma las horas y, si alcanzan el objetivo del plan, convierte automáticamente al voluntario en socio activo (establece la renovación a un año del inicio del ciclo) y dispara el email objetivo_voluntariado_completado.

Ciclo anual

Los voluntarios activos se evalúan anualmente por horas (ver §7): cumplir el objetivo renueva el ciclo; no cumplirlo los devuelve a pendiente_documentacion.

Gamificación (PRO)

Con licencia PRO, Voluntariado_Gamification asigna niveles por track según las horas acumuladas. Tracks por defecto (opción convoca_gamification_tracks):

Track Clave Niveles (horas)
🌱 Naturaleza nature Semilla (0), Brote (10), Árbol (25), Bosque (50), Ecosistema (100)
🤝 Comunidad community Mano Abierta (0), Vínculo (10), Comunidad (25), Ciudad (50), Red (100)
🚀 Crecimiento growth Nivel 1 (0), Nivel 2 (10), Nivel 3 (25), Nivel 4 (50), Nivel 5 (100)

Los niveles se personalizan en Ajustes → Gamificación. Datos disponibles en GET /me/gamification.

16. Emails

Email_Manager envía plantillas HTML (layout premium de convoca-core, naranja #FF8700 + violeta #320028) con variables dinámicas. Las plantillas se editan en Ajustes → Plantillas de Email.

Plantilla Cuándo se envía
solicitud_recibida Al registrarse desde el formulario
bienvenida Al activar a un socio
credenciales_acceso Tras la aprobación, con usuario/contraseña y enlace de acceso
recordatorio_pago, pago_pendiente_2, pago_pendiente_ultimo Recordatorios escalonados de pago pendiente (3, 7 y 14 días)
renovacion, renovacion_15d, renovacion_7d Avisos de renovación 30/15/7 días antes del vencimiento (con botón de pago {link_pago})
renovacion_automatica Al generar un cargo recurrente
renovacion_completada Al completarse el pago de renovación; adjunta el carnet PDF
voluntariado_recordatorio Recordatorio trimestral de horas
objetivo_voluntariado_completado Al completar las horas del plan
confirm_email / verify_phone Confirmación de cambio de email y verificación de teléfono

Variables disponibles (entre otras): {nombre}, {email}, {tipo_miembro}, {plan}, {cuota}, {importe}, {numero_socio}, {fecha_alta}, {fecha_renovacion}, {fecha_baja}, {link_pago}, {usuario}, {password}, {login_url}, {link_confirmacion}.

Proveedor de email

Email_Verifier resuelve el proveedor activo según email_provider:

Proveedor Slug Requisitos
WordPress (wp_mail) wpmail Correo configurado en WordPress (defecto)
Mailgun mailgun API key, dominio y región US/EU

Los proveedores se pueden ampliar con el filtro convoca_email_providers.

17. API REST

Namespace: convoca-members/v1.

Autenticación: las rutas /me/*, /member/* y /documentos/* exigen la sesión de socio (cookie convoca_member_session, o cabecera X-Convoca-Auth). Las respuestas de estas rutas envían cabeceras no-store para que el navegador nunca sirva datos privados en caché. /login, /alta, /proyectos y /search son públicas (con rate limiting).

Método Ruta Descripción
POST /login Inicia sesión (credenciales de WordPress: usuario/email + contraseña). Limitado a 10 intentos / 5 min por IP
POST /alta Alta de socio desde el formulario (nonce convoca_alta_nonce)
GET /me Perfil del socio: id, nombre, email, email_pendiente, direccion, telefono, telefono_verificado, cumpleanos, codigo, estado, plan, forma_pago, fecha_renovacion, fecha_alta, fecha_baja, cuota_vencida, puede_renovar, renovable, tipo
POST /me/profile Editar perfil (dirección, teléfono, email con confirmación, cumpleaños una sola vez) — solo activo
POST /me/verify-phone Enviar enlace de verificación de teléfono
POST /me/confirm-email Confirmar email pendiente con token
POST /me/renovar Renovar membresía (genera pago, devuelve payment_url) — activo y suspendido
POST /me/unsubscribe Solicitar baja (baja_solicitada)
GET /me/card HTML del carnet (admite ?theme=light|dark)
GET /me/certificate Datos del certificado de voluntariado
GET /me/certificate/download Descargar certificado PDF
GET /me/gamification Datos de gamificación (nivel, badges, progreso)
GET /me/pagos Historial de pagos
GET/POST /me/horas Leer horas (cualquier miembro) / registrar horas (solo activo)
GET /me/inscripciones Inscripciones del socio
GET /proyectos Proyectos activos (para el desplegable)
GET /search Búsqueda unificada ?q=&type=&limit= (actividades públicas; socios solo con sesión)
GET /admin/members/search Búsqueda admin de miembros (gestionar_miembros)
GET /documentos/{id} Descarga segura de un documento del socio
GET /member/notifications Notificaciones del socio
POST /member/notifications/read Marcar notificación leída
POST /member/notifications/read-all Marcar todas leídas

18. GDPR y protección de datos

GDPR_Tools se integra con las Herramientas de privacidad de WordPress (Herramientas → Exportar/Borrar datos personales), no con un panel propio:

  • Exportar: registra un exportador de datos de socio que devuelve perfil, consentimiento, pagos, inscripciones y horas en el archivo ZIP estándar de WordPress.
  • Borrar: anonimiza el perfil (los campos PII pasan a [Eliminado], el título a Socio anonimizado #ID), conserva los pagos anonimizados por obligación fiscal, y elimina inscripciones y registros de horas.
  • El consentimiento del alta se registra con GDPR_Tools::log_consent (versión, timestamp e IP).

19. Auditoría y registros

Cada cambio de estado genera una entrada en _convoca_historial (de/a/fecha/usuario/nota) y en la tabla convoca_logs (Logger). Consulta:

  • Miembros → Registros (slug conv-members-logs): log global con nivel, contexto y objeto.
  • Ficha del socio: pestaña de historial de estados y últimos 20 registros de actividad.

20. Shortcodes públicos

Shortcode Descripción
[convoca_alta_socio] Formulario de alta para nuevos socios
[convoca_mi_area] Panel completo del socio (datos, carnet, inscripciones, voluntariado, pagos)
[convoca_mi_perfil] Perfil del socio (estado, plan, inscripciones) — legado, requiere login de WordPress
[convoca_renovar] Renovación de membresía: muestra plan y genera el pago (página /renovar/)
[convoca_voluntariado] Formulario de alta de voluntariado
[convoca_verificar_socio] Verificación pública de membresía (QR del carnet)
[convoca_verificar_certificado] Verificación pública de certificados

21. Bloques Gutenberg

Además de los shortcodes, se registran cuatro bloques (categoría convoca-members):

Bloque Equivalente
convoca-members/formulario-alta [convoca_alta_socio]
convoca-members/mi-area [convoca_mi_area]
convoca-members/formulario-voluntariado [convoca_voluntariado]
convoca-members/verificar-certificado [convoca_verificar_certificado]

22. Cron y automatizaciones

Dos eventos programados (registrados en la activación):

Evento Frecuencia Tareas
convoca_daily_event Diaria Recordatorios de pago (3/7/14 d), avisos de renovación (30/15/7 d), renovaciones automáticas recurrentes, expiración/ciclo de vida (check_member_status), recordatorios trimestrales de voluntariado
convoca_weekly_event Semanal (lunes 08:00) Resumen semanal para administradores (digest_extra_emails)

Los locks atómicos (Utils::acquire_lock) evitan ejecuciones concurrentes y envíos duplicados.

Copia de los correos a la administración

Los correos de socio (alta, bienvenida, credenciales, recordatorios de pago, renovación, certificado, tarjeta PDF y voluntariado) se copian al correo administrador mediante Convoca\Core\Email_Copy, con el mismo canal de envío y marcados con [Copia].

En Ajustes → General hay dos campos relacionados:

  • Email administrador: recibe las copias (si se deja vacío se usa el correo del sitio).
  • Enviar copia de los correos a la administración: interruptor, marcado por defecto.

El correo administrador debe ser distinto del remitente

Si coincide con el remitente configurado, la copia llega al propio remitente y pasa desapercibida.

23. Problemas comunes

Problema Solución
No se envían emails Revisa Miembros → Registros; comprueba el proveedor de email en Ajustes → General (wpmail o Mailgun con su API key/dominio)
Un socio "desaparece" de la zona privada Ha pasado a suspendido o baja por el ciclo de vida: revisa fecha_renovacion y los días de gracia en Ajustes
Socio que se da de alta de nuevo No se duplica: si la ficha está en baja se reactiva (mismo número y antigüedad)
La renovación no amplía la fecha correcta Es intencional: la renovación tardía suma 1 año desde el vencimiento anterior, no desde hoy
No puedo cambiar mi cumpleaños Es intencional: solo se puede establecer una vez
El email de verificación no llega Revisa el proveedor de email y la carpeta de spam; el enlace caduca a las 24 h
No puedo renovar desde el panel Solo activo y suspendido pueden renovar; un baja debe re-darse de alta
El certificado no se genera Se requiere haber completado las horas aprobadas ≥ objetivo del plan
Error al generar PDF Verifica Convoca\Core\Signature (Dompdf) en convoca-core y que chillerlan/php-qrcode esté instalado (composer install)