Modelo de datos canónico

Referencia del modelo de datos de CDC para escribir reportes y consultas. Está pensada para alguien que sabe SQL pero no conoce este esquema, y también para asistentes que traducen preguntas en lenguaje natural a consultas. Todas las consultas corren sobre la base MySQLCDC.

Principio: todo lo reportable vive en tablas centrales

La base es un WordPress multisitio. WordPress guarda el contenido de cada casa en tablas propias por sitio (wp_3_posts, wp_10_postmeta, wp_17_options, …). Para reportar, ignoralas.

CDC copia y denormaliza todo lo que hace falta en tablas centrales (network-wide), para que un reporte:

  • no tenga que unir catorce tablas por casa, y
  • no choque con el problema de collation entre tablas de distintos sitios (un UNION sobre tablas por sitio falla con Illegal mix of collations).

Cada fila central lleva un blog_id (id numérico de la casa) y, en inscripciones, además un site_slug (nombre corto de la casa, como elcascosv). Para aislar por casa, filtrá por esas columnas, nunca eligiendo una tabla por sitio.

Las cuatro tablas que resuelven casi todo:

TablaAlcanceRol
wp_cdc_registrationsnetworkInscripciones. Una fila = una persona inscripta en una actividad.
wp_usersnetworkCuentas de usuario.
wp_usermetanetworkDatos extra del usuario (documento, país del documento, …).
wp_cdc_notesnetworkNotas / bitácora de auditoría. Todavía sin usar en reportes.

1. wp_cdc_registrations — inscripciones

El corazón de casi todo reporte. Una fila representa una persona inscripta en un producto (actividad). No es “una compra”: una orden con dos actividades genera dos filas, y el staff que no pasa por el checkout también tiene su fila.

Muchas columnas son snapshots tomados al momento de inscribir (nombre del producto, fechas, tipo, importes). No se actualizan si después se edita el producto: reflejan “lo que estaba vigente cuando la persona se inscribió”.

Identificación y sitio

ColumnaTipoNotas
idBIGINTClave primaria de la inscripción.
blog_idBIGINTId numérico de la casa. Usalo para joins con otros espejos centrales.
site_slugVARCHARNombre corto de la casa (elcascosv, lagranjasf). Identificador estable para agrupar/filtrar por casa. NULL en filas muy viejas.
product_idBIGINTId del producto (actividad) en su sitio. (blog_id, product_id) identifica una actividad.
order_idBIGINTId de la orden de WooCommerce. NULL en waitlist/staff.
subscription_idBIGINTId de suscripción, si aplica. Normalmente NULL.
registered_idVARCHARClave sintética interna de idempotencia. No aporta a reportes.

Fechas

ColumnaTipoNotas
created_atDATETIME (UTC)Cuándo se hizo la reserva. Usala para “inscripciones por mes”.
updated_atDATETIME (UTC)Última modificación de la fila.
start_datetimeDATETIMECuándo empieza la actividad (snapshot). Usala para “año que asistió”.
end_datetimeDATETIMECuándo termina la actividad (snapshot).

Distinción clave para no confundir números: created_at = cuándo se anotó; start_datetime = cuándo va/fue a la actividad.

La persona inscripta (columnas attendee_*)

ColumnaTipoNotas
attendee_nameVARCHAR NOT NULLNombre de quien asiste. Siempre presente: es el respaldo cuando no hay cuenta.
attendee_emailVARCHAREmail de contacto de la inscripción.
attendee_national_idVARCHARDocumento de quien asiste (snapshot). Ya no es el vínculo con la cuenta —ese rol es de attendee_user_id—. Completarlo ancla la fila sola (ver más abajo). Puede ser NULL (waitlist, pago pendiente, tercero sin documento).
attendee_national_id_countryVARCHAR(5)País emisor del documento (AR, UY, PY, …). Muchas filas NULL.
attendee_birthdayVARCHARFecha de nacimiento. Sin usar en reportes.
attendee_phoneVARCHARTeléfono. Sin usar en reportes.
attendee_originVARCHARProcedencia. Sin usar en reportes.
attendee_meal_restrictionsVARCHARRestricciones alimentarias (texto libre). Sin usar.
attendee_meal_exceptionsJSONExcepciones de comidas por día. Sin usar.
attendee_room_slugVARCHARHabitación asignada. Sin usar en reportes.
attendee_bed_numberVARCHARCama asignada. Sin usar en reportes.
attendee_user_idBIGINTLa cuenta de quien asiste — el ancla de identidad. Es la columna con la que unís a wp_users para saber quién es la persona. NULL hasta que la identidad se confirma (lista de espera nunca la setea; un tercero la recibe por match de documento, al iniciar sesión, o al reclamar “soy yo”). Cuando el comprador es el asistente, attendee_user_id = user_id.
user_idBIGINTEl comprador (cliente de la orden de WooCommerce), no necesariamente quien asiste. Puede ser NULL. Para saber quién asiste usá attendee_user_id, no esta columna.

Tipo de actividad

ColumnaTipoNotas
product_nameVARCHARNombre de la actividad (snapshot).
product_typeVARCHARLista separada por comas de códigos del vocabulario cerrado de red, ordenada alfabéticamente: retiro, convivencia,retiro, retiro,retiro-de-impacto. Códigos válidos: retiro, convivencia, retiro-de-impacto, retiro-de-jovenes, curso-anual. NULL = tipo desconocido (productos históricos sin atributo).
es_retiroTINYINT(1)Columna generada: 1 si product_type contiene la subcadena retiro, si no 0.
es_convivenciaTINYINT(1)Columna generada: 1 si product_type contiene convivencia, si no 0.
es_promocionTINYINT(1)1 si el producto generaba cupones (combo/promoción) al inscribir.

Ver Criterios de tipo de actividad: usá siempre los flags es_*, no compares product_type con =. Los códigos de tipo son de red (no se inventan por casa); el snapshot en la inscripción conserva el valor al momento de anotar.

Estado

ColumnaTipoNotas
attendee_typeVARCHARparticipant (asistentes/compradores/lista de espera) u on-site-staff (coordinadores/ayudantes).
statusVARCHARconfirmed, awaiting_payment, waitlisted, cancelled.
order_statusVARCHAREstado de la orden de WooCommerce, si aplica.
payment_methodVARCHARMétodo de pago.

Ver Estados y su semántica.

Dinero

ColumnaTipoNotas
amountDECIMAL(18,4)Neto cobrado de la línea, después de cupones, sin impuestos. Cubre toda la línea: si quantity > 1, no es precio por persona. NULL = no hubo venta (waitlist, staff) o fila vieja sin backfill.
amount_subtotalDECIMAL(18,4)Lo mismo antes de cupones. amount_subtotal - amount = descuento.
quantityINTUnidades de la línea. Casi siempre 1.
currencyVARCHAR(3)ISO 4217. Hoy uniformemente ARS.

Al sumar importes conviene convertir: SUM(CAST(amount AS DECIMAL(18,4))).


2. wp_users — cuentas

wp_users es la tabla estándar de WordPress. Interesan ID, display_name, user_email y user_registered.


3. wp_usermeta — datos del usuario

wp_usermeta guarda pares (user_id, meta_key, meta_value). Las claves relevantes son las que se editan desde el detalle de un usuario en el panel admin, así que son las que vas a querer traer a un reporte. Los ejemplos de valor son los que hoy ofrece el panel.

meta_keyQué guardaEjemplos de valor
national_idEl documento (DNI) del usuario. Credencial de acceso. Ya no se usa para unir inscripciones con cuentas (eso es attendee_user_id); completar este documento ancla automáticamente las inscripciones huérfanas que lo tengan.30123456
national_id_country_of_issuePaís emisor del documento del usuario.AR, UY, PY
_pa_centroCentro de la persona en esa sede. Guarda el term_id local del atributo WooCommerce pa_centro (por blog). La API lo expone como array de un nombre para mostrar. Cada sede tiene sus propios centros; no es un vocabulario de red.23 (id); en UI: Cebil
relationshipRelación de la persona con la casa. Categoría libre por sede: no hay un catálogo único de red. Guarda el código de la lista de ese sitio, no la etiqueta.códigos de GET /cdc/v1/vocabularies → relationships (varían por sede)
genderGénero.Hombre, Mujer
birthdayFecha de nacimiento (texto).1990-05-17
phoneTeléfono móvil.11 5555-1234

Ids de cliente Debi

El id de cliente en Debi no vive en wp_usermeta (era id_customer_debi, eliminado: en multisite cada casa tiene su propia cuenta Debi y un id de otra casa falla). Queda en la meta del pedido: _debipro_customer_id. Para conciliar pagos, uní por pedido / _debipro_subscription_id, no por usuario.

Vocabularios: tipo de actividad, relación y centro

VocabularioDónde se guardaNotas para reportes
Relaciónwp_usermeta.relationshipPor sede, no es una lista fija de red. Cada casa define sus códigos y etiquetas (WP Admin → Vocabularios del sitio). La meta guarda el código; la lista vigente sale de GET /cdc/v1/vocabularies → relationships. No asumas etiquetas concretas en un reporte multi-sede.
Tipo de actividadsnapshot wp_cdc_registrations.product_type (y atributo WC pa_tipo_de_actividad en el producto)Códigos de red habituales: retiro, convivencia, retiro-de-impacto, retiro-de-jovenes, curso-anual (también se pueden sembrar/editar por sede). Preferí los flags es_* (ver abajo).
Centrowp_usermeta._pa_centroPor sede. Es un term_id del atributo pa_centro de esa sede. El mismo número en otro blog es otro centro. Para mostrar el nombre, uní a wp_N_terms del blog correspondiente (o usá el array de nombres que ya resuelve la API).

Ejemplos de filtro en SQL:

-- Personas por relación (código de esa sede; no hay lista única de red)
WHERE relationship = 'otro'
 
-- Inscripciones por tipo (preferí flags; si necesitás un código puntual)
WHERE FIND_IN_SET('curso-anual', product_type) > 0

Etiquetas vs códigos (relación / tipo)

Las etiquetas del panel salen de GET /cdc/v1/vocabularies de esa sede. Si un reporte necesita el texto legible, mapeá código → etiqueta en la consulta o en Metabase; la meta guarda el código.

_pa_centro y term ids

La meta del usuario es un term_id por blog (o vacío). Un breve experimento guardó slugs de red; la API los resuelve a nombre si el término existe en la sede. Los cupones y atributos de producto también usan term_id por sede.


4. wp_cdc_notes — notas y bitácora (sin explotar aún)

Tabla de notas/auditoría genérica. Una fila es una nota sobre alguna entidad. Todavía no se usa en ningún reporte, así que es terreno fértil para paneles nuevos (actividad administrativa, cuántos emails se enviaron a clientes, etc.).

ColumnaNotas
blog_idCasa. Toda consulta debe scopear por acá.
entity_typeA qué apunta la nota: registration, order, product, customer.
entity_idId de esa entidad (no es FK: las notas sobreviven al borrado del objeto).
author_id / author_nameQuién escribió la nota; NULL/sistema cuando la generó el sistema.
is_system1 = generada automáticamente (transiciones de estado, etc.).
contentCuerpo de la nota.
visible_to_customer1 = la vio el cliente y (si se envió email) la recibió.
email_subject / email_to / email_ccDatos del email, capturados al enviar.
email_sent_atCuándo se envió con éxito. NULL si no se envió o falló.
created_atCuándo se creó la nota (UTC).

Cómo se vincula una inscripción con la persona

El vínculo es directo y numérico: attendee_user_id. Es la cuenta de quien asiste, confirmada al inscribir, al iniciar sesión o al reclamar la inscripción. Uniéndola a wp_users.ID pasás de la inscripción a la cuenta, y desde wp_users + wp_usermeta traés el nombre, el email, el documento y cualquier otro dato del perfil — sin los problemas de collation que trae un join de texto por documento.

attendee_user_id es la persona; user_id es quien pagó

No confundas las dos columnas. user_id es el comprador de la orden; si alguien anota a un tercero, el comprador no es la persona que asiste. Para “quién asistió” usá siempre attendee_user_id.

Lo único a tener presente: no toda inscripción tiene cuenta de asistente. Cuando attendee_user_id es NULL (identidad todavía sin confirmar, o invitado que nunca se registró) no hay perfil que mirar y para agrupar/mostrar caés a lo que quedó guardado en la propia inscripción: attendee_email y attendee_name, que dan más precisión que juntar todos los NULL en un solo grupo.

En una frase

Uní por attendee_user_id ↔ wp_users.ID para traer los datos de la cuenta; si es NULL, caé a attendee_email / attendee_name de la inscripción.

En SQL es un LEFT JOIN con un COALESCE que elige la cuenta cuando existe y cae al dato del invitado cuando no:

SELECT
    r.id                                          AS `Inscripción`,
    r.site_slug                                   AS `Sitio`,
    r.product_name                                AS `Actividad`,
    COALESCE(u.display_name, r.attendee_name)     AS `Persona`,
    COALESCE(u.user_email, r.attendee_email)      AS `Email`,
    CASE WHEN u.ID IS NULL THEN 'Invitado' ELSE 'Con cuenta' END AS `Vínculo`
FROM wp_cdc_registrations r
LEFT JOIN wp_users u ON u.ID = r.attendee_user_id
WHERE r.attendee_type = 'participant';

Con eso ya cubrís el 90% de los reportes. Los detalles de abajo solo hacen falta en casos puntuales.

Los NULL se curan solos

No unas por documento para recuperar identidad. La forma de bajar la cantidad de NULL es operativa: al completar el documento —en la cuenta (perfil, alta o checkout) o en la propia inscripción (corrección del admin)— el sistema ancla automáticamente las inscripciones huérfanas que tengan ese documento, y attendee_user_id deja de ser NULL. Un reporte que hoy muestra invitados sin cuenta va mejorando solo a medida que se cargan documentos, sin tocar el SQL.

La columna user_id, el comprador

wp_cdc_registrations.user_id es la cuenta que pagó, no la que asiste. Solo coincide con attendee_user_id cuando el comprador se anotó a sí mismo. No la uses como identidad del asistente.


Criterios de tipo de actividad

Una actividad puede ser un retiro, una convivencia, ambas a la vez, o ninguna. Los códigos pertenecen al vocabulario cerrado de red (retiro, convivencia, retiro-de-impacto, retiro-de-jovenes, curso-anual). En la inscripción el tipo vive en product_type como lista separada por comas, pero no lo consultes con = ni con IN: te perderías los productos que llevan dos tipos (convivencia,retiro).

Usá siempre las columnas generadas:

  • es_retiro = 1 → la actividad es (algún tipo de) retiro. Matchea por subcadena, así que retiro-de-jovenes, retiro-de-impacto, etc. cuentan como retiro.
  • es_convivencia = 1 → la actividad es convivencia.
  • Ambas en 1 → es retiro y convivencia (existe, y es legítimo).
  • Ambas en 0 → ninguna de las dos o tipo desconocido (p. ej. solo curso-anual).

NULL no es 0

Cuando product_type es NULL (tipo desconocido), ambos flags dan 0. Es decir, “ninguna de las dos” mezcla desconocido con realmente ninguna. Si esa diferencia importa, distinguí product_type IS NULL aparte.

es_promocion = 1 marca los combos que reparten cupones; útil para separar promociones de ventas normales.

SELECT
    YEAR(start_datetime)                        AS `Año`,
    SUM(es_retiro = 1)                          AS `Retiros`,
    SUM(es_convivencia = 1)                     AS `Convivencias`,
    SUM(es_retiro = 1 AND es_convivencia = 1)   AS `Ambos`,
    SUM(es_retiro = 0 AND es_convivencia = 0 AND product_type IS NOT NULL) AS `Ninguna`,
    SUM(product_type IS NULL)                   AS `Tipo desconocido`
FROM wp_cdc_registrations
WHERE attendee_type = 'participant'
  AND status NOT IN ('cancelled', 'waitlisted')
GROUP BY YEAR(start_datetime)
ORDER BY `Año` DESC;

Estados y su semántica

status toma cuatro valores: confirmed, awaiting_payment, waitlisted, cancelled. attendee_type distingue participant de on-site-staff.

La forma canónica de contar, tal como la usan los tableros:

ConceptoDefinición
Vendidasattendee_type = 'participant' AND status NOT IN ('cancelled', 'waitlisted', 'awaiting_payment')
Por pagarattendee_type = 'participant' AND status = 'awaiting_payment'
Lista de esperastatus = 'waitlisted'
Canceladasstatus = 'cancelled'
Staffattendee_type = 'on-site-staff' AND status NOT IN ('cancelled', 'waitlisted')

Por qué Vendidas se define por exclusión y no listando los estados buenos: hubo estados que se retiraron el 10/06/2026 (bed_assigned, arrived, attended, todos migrados a confirmed) que todavía aparecen en filas históricas. Listar status = 'confirmed' dejaría afuera esas filas viejas; excluir los malos las incluye.


Estado Pago Debi (meta de la orden)

Las órdenes pagadas con tarjeta (Debi) guardan un resumen del plan de cobro en meta de la orden WooCommerce. En el panel se muestra como Estado Pago (ver Órdenes → Estado Pago).

No vive en las tablas centrales

Estos campos no están en wp_cdc_registrations. Viven en las tablas por sitio de WooCommerce HPOS (wp_<blog_id>_wc_orders + wp_<blog_id>_wc_orders_meta). Un reporte de red que sume amount de inscripciones no ve deuda de cuotas: para eso hay que mirar la meta Debi (casa por casa, o un UNION por blog_id).

Los valores los escribe el plugin Debi a partir de los webhooks de pago (y un reconcile puntual). No los edites a mano en SQL.

Claves de meta (meta_key)

meta_keyQué esValores / tipo
_debipro_payment_statusEstado Pago del plancurrent (Al día), past_due (Con deuda), paid (Pagado), cancelled (Cancelado)
_debipro_amount_paidSuma acreditada por Debidecimal (string en meta)
_debipro_amount_overdueSuma de cobros fallidos/cancelados que cuentan como deudadecimal
_debipro_amount_remainingfinal_price − amount_paid (piso 0)decimal
_debipro_final_priceTotal del plan con tarjeta (puede incluir recargo por cuotas)decimal
_debipro_installment_countCantidad de cuotasentero
_debipro_installment_amountMonto de cada cuotadecimal
_debipro_subscription_idId del plan/suscripción en Debistring
_debipro_subscription_statusEstado crudo del plan en Debi (active, finished, cancelled, …)string
_debipro_originCómo nació el vínculo con Debiinstallment_plan (checkout en cuotas); otros orígenes de pagos entrantes
_debipro_payments_synced_atÚltima vez que se recalculó la proyeccióndatetime ISO
_debipro_customer_id / _debipro_payment_idIds Debi auxiliaresstring

Cómo se calcula el estado

Para un plan de checkout (_debipro_origin = installment_plan):

Condición_debipro_payment_statusSuele dejar la orden WC en…
amount_paid ≥ final_pricepaidcompleted
Plan cancelado en Debi y aún no está pagocancelledcancelled
amount_overdue > 0past_duesin cambio forzado (sigue processing si ya lo estaba)
Restocurrentprocessing

Dos “estados” distintos

  • wp_cdc_registrations.order_status / estado WC = flujo de la compra (¿confirmó inscripción?).
  • _debipro_payment_status = salud del plan de cuotas.

Una inscripción puede estar confirmed con orden processing y a la vez past_due.

Para reportes de cobranza de tarjeta, el filtro útil es _debipro_payment_status = 'past_due' (y, si hace falta, sumar _debipro_amount_overdue).


Consultas de ejemplo

Todas corren sobre MySQLCDC. Adaptá los WHERE a tu caso.

Inscripciones por casa y estado

SELECT
    site_slug                                   AS `Sitio`,
    SUM(status = 'confirmed')                   AS `Confirmadas`,
    SUM(status = 'awaiting_payment')            AS `Por pagar`,
    SUM(status = 'waitlisted')                  AS `Lista de espera`,
    SUM(status = 'cancelled')                   AS `Canceladas`
FROM wp_cdc_registrations
WHERE attendee_type = 'participant'
GROUP BY site_slug
ORDER BY `Confirmadas` DESC;

Inscripciones por mes de reserva

SELECT
    DATE_FORMAT(created_at, '%Y-%m')            AS `Periodo`,
    COUNT(*)                                    AS `Inscripciones`
FROM wp_cdc_registrations
WHERE created_at IS NOT NULL
GROUP BY DATE_FORMAT(created_at, '%Y-%m')
ORDER BY `Periodo` DESC;

Ingresos por casa (neto de cupones)

SELECT
    site_slug                                   AS `Sitio`,
    SUM(CAST(amount AS DECIMAL(18,4)))          AS `Vendido`,
    SUM(amount IS NULL)                         AS `Sin importe`,
    GROUP_CONCAT(DISTINCT currency)             AS `Moneda`
FROM wp_cdc_registrations
WHERE attendee_type = 'participant'
  AND status NOT IN ('cancelled', 'waitlisted')
GROUP BY site_slug
ORDER BY `Vendido` DESC;

Sin importe debería ser 0 en ventas confirmadas; si no lo es, hay filas vendidas sin importe cargado y el total de Vendido las está dejando afuera silenciosamente.

Personas que más repiten (por asistente)

Agrupá por el ancla attendee_user_id; para las filas de invitado sin cuenta, caé al email (o al nombre) del snapshot para no juntar a todos los invitados en un mismo grupo NULL. No hace falta el documento: esos NULL se van curando solos a medida que se cargan documentos.

SELECT
    COALESCE(MAX(u.display_name), MAX(r.attendee_name)) AS `Persona`,
    COALESCE(MAX(u.user_email), MAX(r.attendee_email))  AS `Email`,
    COUNT(*)                                            AS `Inscripciones`,
    COUNT(DISTINCT r.site_slug)                         AS `Casas`
FROM wp_cdc_registrations r
LEFT JOIN wp_users u ON u.ID = r.attendee_user_id
WHERE r.attendee_type = 'participant'
  AND r.status NOT IN ('cancelled', 'waitlisted')
GROUP BY COALESCE(
    CONCAT('u:', r.attendee_user_id),
    CONCAT('e:', NULLIF(LOWER(TRIM(r.attendee_email)), '')),
    CONCAT('n:', NULLIF(TRIM(r.attendee_name), ''))
)
ORDER BY `Inscripciones` DESC
LIMIT 50;

Retiros vs convivencias por año

Ver el ejemplo en Criterios de tipo de actividad.

Órdenes con deuda de cuotas Debi (una casa)

HPOS: reemplazá 3 por el blog_id de la casa. Los importes vienen como texto en meta; casteá a decimal al sumar.

SELECT
    o.id                                                    AS `Orden`,
    o.status                                                AS `Estado WC`,
    MAX(CASE WHEN m.meta_key = '_debipro_payment_status'
             THEN m.meta_value END)                         AS `Estado Pago`,
    CAST(MAX(CASE WHEN m.meta_key = '_debipro_amount_paid'
                  THEN m.meta_value END) AS DECIMAL(18,4))  AS `Pagado`,
    CAST(MAX(CASE WHEN m.meta_key = '_debipro_amount_overdue'
                  THEN m.meta_value END) AS DECIMAL(18,4))  AS `Adeudado`,
    CAST(MAX(CASE WHEN m.meta_key = '_debipro_amount_remaining'
                  THEN m.meta_value END) AS DECIMAL(18,4))  AS `Restante`,
    CAST(MAX(CASE WHEN m.meta_key = '_debipro_final_price'
                  THEN m.meta_value END) AS DECIMAL(18,4))  AS `Total plan`
FROM wp_3_wc_orders o
INNER JOIN wp_3_wc_orders_meta m ON m.order_id = o.id
WHERE o.type = 'shop_order'
  AND o.id IN (
      SELECT order_id
      FROM wp_3_wc_orders_meta
      WHERE meta_key = '_debipro_payment_status'
        AND meta_value = 'past_due'
  )
GROUP BY o.id, o.status
ORDER BY `Adeudado` DESC;

Para otra casa, cambiá el prefijo wp_3_ (p. ej. wp_10_). No hay tabla central de esta proyección.


Glosario rápido (para traducir preguntas a columnas)

Pensado para resolver preguntas en lenguaje natural.

Si preguntan por…Mirá…
“casa” / “sitio” / “sede”wp_cdc_registrations.site_slug (o blog_id)
“centro” (de la persona)wp_usermeta._pa_centro (term_id por sede) — no confundir con la casa
”relación”wp_usermeta.relationship (código de la lista de esa sede)
“actividad” / “producto” / “retiro” / “convivencia”product_name, product_type, es_retiro, es_convivencia
”cuándo se anotó” / “inscripciones por mes”created_at
”cuándo fue/asistió” / “por año de la actividad”start_datetime
”vendidas” / “confirmadas” / “canceladas” / “lista de espera”status + attendee_type (ver estados)
“staff” / “coordinadores”attendee_type = 'on-site-staff'
”persona” / “quién asistió”cuenta vía attendee_user_id ↔ wp_users.ID; si es NULL, attendee_email / attendee_name
”quién pagó” / “comprador”user_id (no es necesariamente quien asiste)
“documento” / “DNI”attendee_national_id (inscripción) o national_id (usuario). No se usa para unir; completarlo ancla la fila sola.
”ingresos” / “cuánto se vendió”amount (neto), amount_subtotal (bruto), currency
”Estado Pago” / “deuda de cuotas” / “past due” / Debimeta _debipro_* en wp_<blog>_wc_orders_meta (ver )
“promoción” / “combo”es_promocion
”notas” / “emails al cliente”wp_cdc_notes (content, visible_to_customer, email_sent_at)

Reglas que un asistente no debe olvidar:

  1. No toques tablas por sitio (wp_N_*). Todo lo reportable está en las cuatro tablas centrales.
  2. Filtrá por site_slug/blog_id para aislar una casa, no por nombre de tabla.
  3. es_retiro/es_convivencia, no product_type = '...'.
  4. Vínculo persona↔inscripción por attendee_user_id (↔ wp_users.ID); cuando es NULL, caé a attendee_email / attendee_name del snapshot (no unas por documento). user_id es el comprador, no el asistente. Los NULL se curan solos al completar documentos.
  5. Pre-agrupá wp_usermeta antes de unir, para no inflar los conteos.
  6. NULL ≠ 0 en product_type y en los importes.
  7. relationship / códigos de tipo: filtrá por código, no por etiqueta. relationship y _pa_centro son por sede (ver Vocabularios: tipo de actividad, relación y centro).