{{ brandDisplay.nombre }}

Panel de Administración

Abre tu app de autenticación (Google Authenticator, etc.)

{{ errorCsrf }}

{{ errorLogin }}

{{ brandDisplay.nombre }}

Panel de Administración

Notificaciones recientes

Sin notificaciones.

  • {{ n.mensaje || n.titulo }}

    {{ formatearFechaHoraUnix(n.creado_en) }}

{{ pullRefreshing ? 'Actualizando…' : pullRefreshY > 50 ? 'Suelta para actualizar' : 'Tira para actualizar' }}

Dashboard

LTV promedio

${{ cohortes.ltv_promedio }}

Repeat rate

{{ cohortes.tasa_repeat_pct }}%

Repeat clients

{{ cohortes.clientes_repeat }}

Proyección mes

${{ predictivo.proyeccion_mes }}

Ingresos mes

${{ predictivo.ingresos_mes }}

Clientes en riesgo

{{ predictivo.clientes_riesgo }}

Mejor día

{{ predictivo.mejor_dia_semana }}

Ocupación en vivo

{{ ocupacionLive.en_sala }} / {{ ocupacionLive.capacidad }} salas · {{ ocupacionLive.pct }}%

Responsive health (R136)

{{ responsiveHealthData.health_score }}/100

{{ responsiveHealthData.enabled_features ?? '—' }} features activas

{{ s.nombre }}

{{ s.turnover_post_checkout ? 'Limpieza post-checkout' : s.estado }}

Portal ingeniero Status

{{ dashboard.error }}

Cargando resumen…

Gestión de Salas

{{ errorSalasAdmin }}

No hay salas registradas

{{ sala.nombre }}

{{ formatMoneda(sala.precio_hora) }}/hr

Ingenieros y PINs

Portal: ingeniero.html — login con ID + PIN de 6 dígitos

{{ errorIngenierosAdmin }}

Cargando ingenieros…

No hay ingenieros registrados

#{{ ing.id }} · {{ ing.nombre }}

{{ ing.activo ? 'Activo' : 'Inactivo' }} · Comisión {{ ing.comision_pct }}%

{{ formatMoneda(ing.precio_hora) }}/hr

PIN: {{ pinVisibleIds[ing.id] ? (ing.portal_pin || '—') : '••••••' }}

Catálogo comercial

Membresías, créditos, proyectos y regalo — visible en index#planes-paquetes

Solo el superadmin puede editar precios y paquetes. Puedes ver el catálogo en modo lectura.

{{ errorCatalogoAdmin }}

Cargando catálogo…

Orden ID Nombre Precio Detalle Marketing Activo Popular Acciones
Sin ítems en esta categoría
{{ item.id }} {{ item.nombre }} {{ formatMoneda(item.precio || 0) }} {{ catalogoDetalleItem(item) }} {{ item.marketing || '—' }}

Viene del calendario

Día: {{ formatearFechaCorta(retornoCalendario.calDiaSeleccionado) }}

Reservas

Actualizado: {{ ultimaActualizacion }}

Gestión de pagos

Más recientes primero. Revisa folios duplicados y totales antes de confirmar la reservación.

El cliente puede subir comprobante en Consultar reserva (folio + teléfono); al subirlo, la solicitud pasa a revisión de pago automáticamente.

{{ errorGestionPagos }}

Cargando…

No hay reservas en gestión para el filtro «{{ filtroReservaChip }}».

{{ gp.estado === 'pendiente' ? '← Rechazar solicitud' : '← Rechazar reservación' }}
{{ gp.estado === 'pendiente' ? 'Confirmar pago →' : 'Confirmar reservación →' }}

{{ gp.cliente }} · {{ gp.salaNombre }}

{{ gp.fecha }} · {{ gp.hora_inicio }}–{{ gp.hora_fin }}

Folio pago: {{ gp.folio_pago || '—' }} · Res: {{ gp.folio_reservacion || '—' }}

WhatsApp: {{ gp.telefono }}

Sin teléfono registrado

{{ formatMoneda(gp.total) }} (esperado {{ formatMoneda(gp.total_esperado) }})

{{ gp.estado === 'revision_pago' ? 'Pago recibido, en revisión' : gp.estado === 'pendiente' ? 'Pendiente de pago' : gp.estado }} Folio en otra solicitud Total no cuadra Con comprobante
Ver comprobante

CRM — Ficha de cliente

{{ errorCrm }}

Confirmadas

{{ fichaCliente.stats.confirmadas }}

Canceladas

{{ fichaCliente.stats.canceladas }}

Rechazadas

{{ fichaCliente.stats.rechazadas }}

Gastado

{{ formatMoneda(fichaCliente.stats.gastado) }}

Score: {{ scoreClienteData.score }}/100 ({{ scoreClienteData.nivel }})

Paquete / créditos

Reservas recurrentes

  • {{ rc.nombre_cliente }} · día {{ rc.dia_semana }} · {{ formatHoraAdmin(rc.hora_inicio) }}–{{ formatHoraAdmin(rc.hora_fin) }}

Nueva reserva recurrente

Historial consumos paquete

  • {{ c.horas }} h · reserva #{{ c.reserva_id }} · {{ formatearFechaHoraUnix(c.creado_en) }}

Referidos venue B2B (8% comisión)

Escuelas, aliados y venues. Comparte el link con ?venue=VENUE-XXXXXX en index.html.

Nuevo código: {{ venueNuevoCodigo }} {{ venueLinkNuevo }}

Venue Código Reservas Comisión acum.
{{ v.venue_nombre }} {{ v.codigo }} {{ v.reservas_count }} {{ formatMoneda(v.comision_acum) }}

Sin venues registrados.

Historial de reservas

Reservas ya procesadas (confirmadas, rechazadas, canceladas o no-show). Toca una solicitud en gestión de pagos para ver el historial de ese teléfono.

{{ gestionClienteContexto.cliente }}

{{ gestionClienteContexto.telefono || 'Sin teléfono' }}

Cliente nuevo Cliente recurrente · {{ gestionClienteContexto.stats.confirmadas }} confirmada(s) {{ gestionClienteContexto.stats.total_reservas }} reserva(s) en total

Mostrando {{ reservas.length }} de {{ historialTotal }}

{{ errorReservas }}

No hay reservas en el historial para «{{ filtroReservaChip }}»

{{ reserva.cliente }}

{{ reserva.estado }}

Folio: {{ reserva.folio_reservacion }}

WhatsApp: {{ reserva.telefono }}

Sala: {{ reserva.salaNombre }}

Fecha: {{ reserva.fecha }} · {{ reserva.hora_inicio }}–{{ reserva.hora_fin }}

Ediciones cliente: {{ reserva.ediciones_cliente ?? 0 }}/3

{{ formatMoneda(reserva.total) }}

Calendario de ocupación

{{ errorCalendario }}

{{ calNombreMes }}

{{ dia }}
Disponible Saturado Bloqueado Seleccionado

{{ calDiaSeleccionado ? formatearFechaCorta(calDiaSeleccionado) : 'Selecciona un día' }}

Estado: {{ calDiaInfo.estado }} · {{ calDiaInfo.motivo }}

Haz clic en un día del calendario para ver las reservas activas.
Sin reservas activas este día.

Timeline del día

{{ slot.label }}
Libre

Lista de espera

Clientes que pidieron aviso cuando se libere un horario.

{{ listaEspera.error }}

Cargando…

Sin entradas activas.

  • {{ le.nombre_cliente }} · {{ le.fecha }}

    {{ le.salaNombre }} · {{ le.telefono }} · {{ le.estado }}

    WhatsApp

Recordatorios pre-sesión

Sesiones confirmadas para la fecha seleccionada (por defecto: mañana).

{{ recordatorios.error }}

Cargando…

No hay sesiones confirmadas para esa fecha.

  • {{ rec.cliente }} · {{ rec.salaNombre }}

    {{ formatHoraAdmin(rec.hora_inicio) }}–{{ formatHoraAdmin(rec.hora_fin) }}

    Enviar WhatsApp

Historial cron (últimas ejecuciones)

Cargando…

  • {{ formatearFechaHoraUnix(log.creado_en) }} · {{ log.fecha_objetivo }} · {{ log.total_sesiones }} sesión(es) · {{ log.origen }}

Sin ejecuciones registradas. Usa ./cron_recordatorios.sh en crontab.

Horarios por Sala

El horario aplica a todo el estudio (todas las salas comparten el mismo espacio).

No hay salas registradas. Crea salas primero para configurar sus horarios.

{{ sala.nombre }}

{{ diasSemana[dia] }}

{{ errorHorarios[sala.id] || 'Error al cargar horarios' }}
{{ h.hora_apertura }}:00 - {{ h.hora_cierre }}:00

Cerrado

Cargando...

Fechas Bloqueadas

{{ errorFechas }}

No hay fechas bloqueadas

{{ fecha.fecha }}

{{ etiquetaTipoBloqueo(fecha) }}

{{ fecha.motivo }}

Auditoría

  • {{ log.accion }}

    {{ log.detalle }}

    {{ log.admin_usuario }} · {{ formatearFechaHoraUnix(log.creado_en) }}

Configuración del sitio

Visible en la página pública de reservas

Estos datos aparecen en el carrito cuando el artista o banda va a pagar, y el WhatsApp se usa al confirmar la reserva.

{{ errorConfigSitio }}

Cargando...

Si está vacía, el artista o banda verá un aviso para solicitar datos por WhatsApp.

10 dígitos sin espacios. Se usa para abrir el chat al confirmar reservas.

Recibirás avisos de nuevas reservas y horarios liberados (lista de espera). Déjalo vacío para desactivar.

Correo saliente (SMTP)

Recomendado en producción. Si está vacío, se usa mail() del servidor.

WhatsApp automático (Meta Cloud API)

OTP del portal, confirmaciones y recordatorios. Guía paso a paso: docs/META-WHATSAPP.md · Meta Cloud API

Guía rápida Meta (clic para expandir)
  1. Crea app en developers.facebook.com → tipo Negocios → agrega producto WhatsApp.
  2. En WhatsApp → API Setup copia el Phone number ID (no es tu teléfono).
  3. Genera token permanente (Usuario del sistema en Business Settings) o usa token temporal 24 h para pruebas.
  4. Modo dev: agrega tu celular en To (números permitidos).
  5. Pega credenciales abajo → Guardar cambiosEnviar WhatsApp de prueba.
  6. Producción: crea plantillas en WhatsApp Manager y pon el nombre exacto en los campos de abajo.

CLI: npm run configure:wa -- --probar (desde site_config.local.php)

Token WA guardado en el servidor
Sin token WA — completa Phone Number ID + token y guarda

Guarda antes de probar. La prueba va al WhatsApp de contacto de arriba ({{ configSitio.whatsapp || 'sin configurar' }}).

Plantillas Meta (opcional — si falla, usa texto libre)

Reservas pendientes

Cron sugerido: cron_cancelar_pendientes.sh cada hora.

Tiempo que el frontend mostrará al usuario antes de reintentar tras un 503 por SQLite ocupado.

Autenticación en dos pasos (2FA)

2FA activo en tu cuenta.

Protege el panel con código TOTP.

Escanea en tu app o ingresa manualmente:

{{ totpSetup.secret }}

Abrir en app

Reglas, ubicación y pagos

Mercado Pago

Checkout activo Token OK · falta activar Sin configurar

Credenciales en developers.mercadopago.com → tu app → Credenciales de prueba (sandbox).

Webhook (pegar en MP → Webhooks → Pagos):

{{ reglasSitio.mp_webhook_url || '—' }}

Producción: define SITE_URL=https://tu-dominio.com en el servidor para que el webhook use la URL pública correcta.

Firma webhook: variable MERCADOPAGO_WEBHOOK_SECRET (clave en MP → Webhooks). Modo actual: {{ reglasSitio.mp_webhook_modo || 'warn_only' }}. Con MERCADOPAGO_WEBHOOK_STRICT=1 se rechazan webhooks sin secret.

SMTP: {{ saludSistema.smtp_configurado ? 'OK' : 'No' }} · WA: {{ saludSistema.whatsapp_configurado ? 'OK' : 'No' }} · WAL: {{ saludSistema.wal_mode || '—' }}

BD: {{ saludSistema.db_size_bytes ? Math.round(saludSistema.db_size_bytes/1024) + ' KB' : '—' }} · Webhook: {{ saludSistema.webhook_configurado ? 'Sí' : 'No' }}

Seguridad: SITE_URL {{ saludSistema.site_url_configurada ? '✓' : '✗' }} · APP_SECRET {{ saludSistema.app_secret_configurada ? '✓' : '✗' }} · MP webhook secret {{ saludSistema.mp_webhook_secret_configurada ? '✓' : '✗' }} ({{ saludSistema.mp_webhook_modo || 'warn_only' }})

Cron sugerido: php api/backup_db.php y php api/generar_reservas_recurrentes.php --semanas=2

  • {{ cr.nombre }} · {{ formatearFechaHoraUnix(cr.creado_en) }}

Cupones de descuento

  • {{ cp.codigo }} {{ cp.tipo === 'fijo' ? '$' + cp.valor : cp.valor + '%' }} · {{ cp.usos_actuales }}/{{ cp.usos_max || '∞' }}

Administradores

  • {{ ad.usuario }} · {{ ad.rol }}

White-label (marca)

Muro «Grabaron aquí»

  • {{ m.artista }} — {{ m.quote?.slice(0,40) }}

Open mic — curación

  • {{ om.participante }} · {{ om.fecha }} · {{ om.estado }} Demo

Reportes de Ingresos

Cargando reporte…

{{ reportes.error }}

Conversión del mes

Tasa confirmación: {{ reportes.datos.conversion.tasa_confirmacion_porc != null ? reportes.datos.conversion.tasa_confirmacion_porc + '%' : '—' }}

Pendientes

{{ reportes.datos.conversion.pendientes }}

Confirmadas

{{ reportes.datos.conversion.confirmados }}

Rechazadas

{{ reportes.datos.conversion.rechazados }}

Canceladas

{{ reportes.datos.conversion.cancelados }}

Ingresos

{{ formatMoneda(reportes.datos.total_general.ingresos) }}

Ocupación por franja horaria

Reservas confirmadas vs capacidad estimada (salas × días operativos)

Sin datos

{{ f.franja }} {{ f.reservas }} · {{ f.ocupacion_porc }}%

Ingresos por Día

Sin datos

{{ formatearFechaCorta(fila.fecha) }}

{{ formatMoneda(fila.ingresos) }}

{{ fila.reservas }} res.

Top 10 Artistas-Bandas

Sin datos

{{ cliente.cliente }}

{{ cliente.telefono }}

{{ cliente.reservas }} reservas {{ formatMoneda(cliente.gastado) }}

Día de la semana y no-shows

Reservas confirmadas por día

Sin datos
{{ d.dia }} {{ d.reservas }}

No-shows (cancelados + rechazados)

{{ reportes.datos.no_shows ?? 0 }}

Tasa no-show: {{ reportes.datos.tasa_no_show }}%

Embudo de conversión

Visitas

{{ reportes.datos.funnel.vista_index || 0 }}

Carrito

{{ reportes.datos.funnel.carrito_add || 0 }}

Reservas

{{ reportes.datos.funnel.reserva_creada || 0 }}

Comprobantes

{{ reportes.datos.funnel.comprobante_subido || 0 }}

Confirmadas

{{ reportes.datos.funnel.reserva_confirmada || 0 }}

Conv. reserva

{{ reportes.datos.funnel.conversion_reserva || 0 }}%

Días con baja ocupación (≤2 sesiones)

{{ hm.fecha }} {{ hm.slots_ocupados }} ses.

Clientes recurrentes (2+ sesiones)

Sin datos

{{ c.cliente }}

{{ c.telefono }}

{{ c.sesiones }} ses.

{{ formatMoneda(c.gastado) }}

Heatmap demanda (Round 15)

Sugerencias precio:

  • Día {{ s.dow }} h{{ s.hora }} → {{ s.accion }} ({{ s.motivo }})

Sin datos o pulsa Actualizar

Guía de uso
Accesos rápidos:

Pro R127–136 (Mobile-first · 520 total)

Mantenimiento salas

  • {{ m.fecha }} · {{ m.salaNombre }} · {{ m.hora_inicio }}–{{ m.hora_fin }}h · {{ m.motivo }}

Pro R19

Google Calendar sync


No-show auto · grace {{ configR19.no_show_grace_min }}min · fee {{ configR19.no_show_fee_pct }}%

Upload entrega

Pro R21

Revisiones · Mastering · Paquetes pro

Paquetes: Catálogo comercial · Vista index · Pase: pase.html

Pro R22–R25

  • #{{ c.id }} {{ c.rfc_cfdi }}

Depósito daños

Proyección: ${{ forecastOps.ingreso_proyectado }}

MP ${{ conciliacionOps.mercadopago }} · Diff ${{ conciliacionOps.diferencia }}

Riesgo {{ churnOps.riesgo }} ({{ churnOps.churn_score }})

Pro R32 (Fase 9–12)

  • {{ g.codigo_qr }} · {{ g.nombre }} · {{ g.estado }}

Upsell check-in

Tenant sede

{{ tenantSedeInfo.nombre }} · {{ tenantSedeInfo.salas_count }} salas

Pro R33–38 (Fase 13–18)

LTV ${{ rentScore.ltv }} · score {{ rentScore.score }}

Status page

Pro R117–126 (Fase 97–106 · 470 total)

Pro R103–116 (Fase 83–96 · 420 total)

Pro R93–102 (Fase 73–82)

Pro R81–92 (Fase 61–72)

Pro R73–80 (Fase 53–60)

Pro R65–72 (Fase 45–52)

Pro R57–64 (Fase 37–44)

Pro R47–56 (Fase 27–36)

Pro R39–46 (Fase 19–26)

Pro R26–R30

Integraciones (R26–30)

Slack OK Discord OK Drive OK Dropbox OK Stripe OK Google Reviews OK

Sedes

Equipos

Labels B2B

Roles por sede: operador ve solo su sede

{{ margenOps.salas?.length || 0 }} salas

Herramientas R15

TV: tv-dashboard.html

Kiosco: kiosco.html

Widget: widget.html ·

Salas: sala.html

{{ reservasBulkSel.length }} seleccionada(s)
  • {{ c.label }}

⌘K / Ctrl+K · Esc cerrar