EN
SOP-IT-003 · v3.0
Actualizado 5 Jul 2026

BrandaCare Metrics & Dashboards

Sistema multi-cliente sobre Open Dental (API) + OPS Google Sheet + Stedi

Propósito

El sistema genera, por ciclo de facturación y por cliente, un dashboard embebido en WordPress (brandacare.com) con: dinero cobrado del seguro, A/R recuperado, verificaciones de seguro y no-shows. El widget es UNO SOLO y modular: prende/apaga tarjetas según el "tier" del cliente. Todo corre solo, a diario, con GitHub Actions, y se publica por SFTP a Cloudways.

Acceso restringido: contiene keys de OD, Stedi, SFTP y GitHub. No mostrar al equipo operativo.

Ficha 1 — Arquitectura, tiers y roster de clientes

Flujo general

Open Dental (API) + OPS Google Sheet (tab "ALL DATA", filtrado por "Doc")
                              ▼
                     recovery_metrics.py
                              ▼
                output/metrics_<id>.json
                              ▼
        widget en /dashboards/ de Cloudways
                              ▼
         iframe en WordPress (gateado por email)

run_pipeline.py orquesta todos los clientes; GitHub Actions lo corre a diario y sube los JSON + el widget por SFTP.

Tiers (definen qué tarjetas ve el cliente)

TierTarjetasEjemplos
billing+insurance (full)Total recovered lifetime + A/R gráfico 6m + Insurance collected + A/R recovered + Verificaciones + No-showsHallandale, Casas
billing+arTotal recovered lifetime + A/R gráfico + Insurance collected + A/R recovered. SIN verificacionesBenitez
verifications (verif-only)SOLO verificaciones + Solstice + No-shows. Oculta todo lo de dinero, aunque OD del cliente tenga esos números. Salta la extracción de pagos → corre rápidoA+

Roster de clientes (jun 2026)

ClienteTier / FuenteEstado dashboardNotas
Hallandale Dental Carebilling+insurance (OD API)EN VIVOMVP
Benitez Dental Centerbilling+ar (OD API)EN VIVOLifetime A/R $13,192
A+ Dental of Aventuraverifications (OD API)EN VIVONo-show por Unscheduled List
Dr Casas Family Dentistrybilling+insurance (OD API)PENDIENTEEsperar más A/R para reflejar el trabajo
G-DentalDentrix (sin API)FUTUROCross-match OPS + reportes Dentrix
Le DentisteDentrix (sin API)FUTUROCross-match OPS + reportes Dentrix

Ficha 2 — Métricas de dinero (recovery_metrics.py)

Las 3 cifras (definiciones exactas)

CifraDefinición
TOTAL RECOVERED (lifetime)A/R recuperado acumulado desde service_start_date.
INSURANCE COLLECTED (ciclo)TODO lo cobrado del seguro en el ciclo, sin importar antigüedad.
A/R RECOVERED (ciclo)Subconjunto cobrado con DOS de 45+ días — claims viejos que requirieron seguimiento.

Regla de "recuperado" (crystal clear)

  • A/R recuperado = pago posteado MÁS de 45 días después del DOS (CheckDate − ProcDate > 45).
  • El piso de fecha es sobre la FECHA DEL PAGO (>= service_start_date), NO sobre el DOS. Un claim con DOS viejo (aun de Dentemax) cobrado durante nuestra gestión SÍ cuenta.
  • Código por código: cada claimproc cuenta literal. NO se deduplica por importe.

Fuentes y filtro

Flags nuevos

FlagFunción
Verif-only (tier=verifications)Se SALTA od_insurance_payments (do_billing=False) → mucho más rápido y no muestra dinero.
--as-of YYYY-MM-DDCorta el conteo a esa fecha (snapshot del día, ej. para comparar con un reporte manual).

Comando y outputs

python recovery_metrics.py --client <id> \
    --ops-sheet <SHEET_ID> --ops-tab "ALL DATA" \
    [--as-of 2026-06-28] --debug

Ficha 3 — Verificaciones de seguro

Qué cuenta

Valor (referencia ADA — actualizado)

MétricaValorNota
Tiempo ahorrado por verificación10 minutosMINUTES_SAVED_PER_VERIFICATION = 10, igual que la web
Dinero ahorrado por verificación$5.63ADA. Implica ~$33.78/hora — mayor que sueldo crudo ($20-22) porque captura errores/denials evitados
Editable por clienteverification_value_per_usdConfig del cliente

Solstice (cortesía) — NUEVO

Regla clave:
  • Se identifica por el carrier: el detalle de verificaciones ahora trae "Ins Carrier" (del OPS) y un flag "Solstice?".
  • solstice_count = verificados cuyo carrier contiene "solstice".
  • El carrier se cruza por paciente desde el OPS; puede no conocerse para verificaciones que sólo vienen de notas OD (sin match en OPS), así que solstice_count es un piso.
Nunca usar "free": las Solstice y no-shows se muestran como "complementary · on us" / "no charge".

Ficha 4 — No-shows (dos definiciones según el cliente)

Default (broken en el calendario)

A+ y similares: noshow_source = "unscheduled" — NUEVO (clave)

Problema resuelto: A+ NO marca "Broken": manda la cita al Unscheduled List (AptStatus=UnschedList), que no tiene fecha de calendario, por eso el escaneo día-por-día daba 0.

Corte a HOY (no contar el futuro) — fix jul-2026

Fix crítico:
  • En un ciclo EN CURSO se cuenta solo hasta hoy: ce_eff = min(fin_de_ciclo, hoy).
  • Antes tomaba todo el ciclo (hasta el fin), y agarraba citas del Unscheduled con AptDateTime futuro (tratamientos planificados aún no ocurridos) como si fueran no-shows.
  • Firma del falso positivo: DateTStamp ANTERIOR al AptDateTime (cita editada antes de su fecha = futura). Un no-show real se marca en/después del día de la cita.
  • El flag --as-of usa la misma lógica para reproducir un snapshot de un día pasado.

Ficha 5 — Modelo A+ (verifications-only) — NUEVO

A+ paga SOLO verificaciones (no billing ni A/R). El dashboard muestra el número que paga bien claro y suma las cortesías para inflar el valor total, validando el precio.

CategoríaFórmula
Billable (lo que paga)Verificaciones completadas − Solstice
Cortesía (no charge)Solstice + No-shows (verificados, no vinieron)
Total verifiedCompletadas + no-shows

La matemática de valor (horas y $) va sobre el TOTAL.

Ejemplo (Jun 2026):
294 billable + 30 Solstice + 137 no-show = 461 total verified76.8 hrs$2,595 (vs ~$1,000/mes que pagan → 2.4x)

Campos JSON

billable_verifications, solstice_count, broken_appointments,
total_verified, verification_value_total_usd, hours_saved_total

Ficha 6 — A/R Recovery (procedimientos sin claim)

Encuentra procedimientos completados SIN claim en Open Dental (post-Dentemax), identifica los facturables y permite enviarlos al seguro.

ScriptFunción
find_unbilled_procedures.pyBuscador
monthly_report.pyReporte Excel cliente
send_unbilled_week.pyEnvío 837D

Ficha 7 — Widget del dashboard (modular)

Archivo y modularidad

UNA plantilla para todos: web/brandacare_recovery_widget.html lee el JSON por ?data= y prende/apaga tarjetas según d.modules (derivado del tier). Retrocompatible: si no viene modules, se infiere del tier.

Layouts

TierLayout
fullIzq LIFETIME (total + gráfico A/R 6m); der THIS CYCLE (collected, A/R, verificaciones, no-shows)
verif-only (A+)Tarjeta grande de billable (número azul) + "Total verified/horas/$"; a la derecha 2 tarjetas grises: Solstice ("complementary · on us") y No-shows ("no-show · no charge")

Ficha 8 — Embed en WordPress (WPCode + Elementor)

Snippet PHP (WPCode)

web/wordpress_dashboard_snippet.php: shortcode [brandacare_dashboard]. Un array $clients mapea email de login → client_id, y muestra el iframe con el metrics_<id>.json que corresponde.

Sumar un cliente: una línea nueva en el array. El admin puede previsualizar cualquiera agregando ?client=<id> a la URL.

Cache-buster

El ?t=time() va en la URL del JSON (metrics_<id>.json?t=...), NO solo en el widget. Así la web nunca sirve datos viejos. Tras un cambio, purgar Breeze (Purge All Cache) y refrescar con Cmd+Shift+R.

Gris a todo el ancho

Wrapper con box-shadow + clip-path (full-bleed) y padding lateral para centrar/achicar el contenido. Alternativa nativa: sección Elementor con fondo #E2E6F0 y Content Width = Boxed.

Pasos de publicación en WordPress

1WPCode → editar el snippet → pegar la versión nueva → Update

2Si hay Breeze, Purge All Cache

3Refrescar con Cmd+Shift+R

4Página del cliente: shortcode [brandacare_dashboard]

Una sola página sirve para todos.

Ficha 9 — Deploy automático (GitHub Actions + Cloudways)

Componentes

ArchivoFunción
run_pipeline.pyLista CLIENTS=[...] y corre recovery_metrics por cada uno (alertas por email si algo falla)
.github/workflows/daily.ymlCron diario (08:00 UTC) + workflow_dispatch (botón manual). Restaura credenciales desde secrets, corre el pipeline, sube por SFTP (lftp) los metrics_*.json + el widget HTML a Cloudways

Secrets (Settings → Secrets and variables → Actions)

SecretContenido
SERVICE_ACCOUNT_JSONGoogle service account (para OPS Sheet)
ENV_FILE.env con keys generales
CLIENT_HALLANDALE, CLIENT_BENITEZ, CLIENT_APLUSJSONs de config por cliente
SFTP_HOST/USER/PASS/PATHCredenciales Cloudways
Higiene del repo (PRIVADO): el .gitignore excluye llaves (clients/*.json, .env, service_account.json) y datos de pacientes (*.pdf, *.csv, BCBOT/, out/, screenshots). Nunca subir PHI. Usar git add con criterio (add -A agarra TODO).

Deploy manual (cuando cambia código o widget)

git add -A && git commit -m "..." && git push

Luego: GitHub → Actions → BrandaCare daily metrics → Run workflow → esperar el check verde (5-8 min).

Auth de git: token (PAT classic) con scope repo + workflow (usuario ybrandacare, password = ghp_...).

Ficha 10 — Replicar a un cliente nuevo

1Crear clients/<id>.json

Con: open_dental (keys), stedi, provider, google_sheet (sheet_id, tab_prefix), tier, cycle_start_day, service_start_date, ops_doc_name, verification_value_per_usd (5.63).

Para verif-only: tier="verifications", noshow_source="unscheduled", tier_verifications_included, monthly_fee.

2Confirmar el nombre EXACTO de la columna "Doc" del OPS

Ponerlo en ops_doc_name.

3Compartir el OPS con el service account (lector)

claims-audit-bot@claims-audit-495602.iam.gserviceaccount.com

4Correr recovery_metrics con --debug

Validar 2-3 pagos/verificaciones contra Open Dental.

5Agregar el client_id a CLIENTS en run_pipeline.py

6Crear el secret CLIENT_<NOMBRE> en GitHub

Y restaurarlo en daily.yml (printf a clients/<id>.json).

7Agregar el email del cliente al array $clients

Del snippet WPCode.

8git push → Run workflow → verificar el dashboard

Con ?client=<id>.

Widget v1 (frozen, referencia)

El widget v1 congelado sigue vivo como referencia del layout original de Hallandale. Está en brand/widgets/brandacare_recovery_widget_v1.html.

Nota: el v1 NO es el que corre en producción — el actual es modular (web/brandacare_recovery_widget.html). El v1 se conserva como snapshot histórico.