Feature Specification: Panel de analítica de CitaClara

Feature Branch: 004-panel-analitica

Created: 2026-09-29

Status: Draft

Input: User description: "Panel de analítica para la clínica. Sara lo necesita para las renovaciones: "enseñar a la clínica lo que CitaClara le ahorra". Alcance: una página del panel (misma clave que la agenda), con interfaz moderna y gráficos claros: ocupación semanal por profesional, tasa de no asistencia por profesional, ingresos por servicio (citas completadas; importes exactos, al céntimo), y evolución de las últimas 8 semanas. Solo lectura: esta feature no escribe NADA."

Clarifications

Session 2026-09-29

Números reales de la semilla v1 (SEMILLA_PRNG=1): 409 citas en la historia de 8 semanas (335 completadas, 41 no_asistidas, 33 canceladas) + 82 reservadas futuras.

  • Q: ¿Cómo se calcula la «tasa de no asistencia» de cada profesional? → A: no_asistidas entre TODAS sus citas de la historia de 8 semanas, incluidas las canceladas (coherente con el reparto 82/10/8 de la 001).
  • Q: ¿Cómo se calcula la «ocupación semanal» de cada profesional? → A: minutos de citas completadas entre minutos de jornada (las canceladas liberan el tramo y las no_asistidas no produjeron visita).
  • Q: ¿Cómo se presenta la ocupación en la página? → A: un único número por profesional (media de las 8 semanas).

Session 2026-09-30

  • Q: ¿De dónde sale el precio con el que se calculan los ingresos de cada cita completada: del precio que tenía el servicio el día de la cita, o del precio actual del servicio? → A: el precio se congela en la cita al reservarla; cambiar el precio de un servicio NO altera los ingresos de citas ya cerradas.
  • Q: ¿Qué fija la fecha de «hoy» que delimita las 8 semanas de la analítica: la fecha en que se cargó la semilla, o el día en que Sara mira la página? → A: se ancla en «hoy» cada vez que se abre la página (lunes de la semana en curso, Europe/Madrid); son las 8 semanas cerradas anteriores y la semana en curso queda fuera. El determinismo se exige para la misma ventana.
  • Q: Si Sara entra con la clave de su clínica, ¿los números y gráficos de la analítica pueden incluir citas de otras clínicas? → A: no; todos los agregados se limitan a la clínica de la clave con la que se entra.
  • Q: Si la clínica da de baja a un profesional o a un servicio, ¿sus citas y sus ingresos siguen apareciendo en la analítica? → A: sí; la baja solo impide reservar. Todos los agregados cuentan las citas de la ventana tanto de profesionales y servicios activos como dados de baja.
  • Q: ¿Qué tiene que cumplir la interfaz para que se pueda decir que es «moderna y limpia», de forma que se pueda comprobar? → A: con una lista de requisitos comprobables (gráfico real con eje y valores, letra ≥ 16 px en móvil, contraste ≥ 4,5:1, las cuatro métricas en pantalla y cero jerga técnica).

User Scenarios & Testing

User Story 1 — Tasa de no asistencia por profesional para la renovación (Priority: P1)

Sara abre la página de analítica y ve, por cada profesional (María, Jorge, Lucía), su tasa de no asistencia en un gráfico de barras claro, para enseñarle a la clínica cuántas citas se pierden por incomparecencia y argumentar la renovación.

Why this priority: Es el argumento comercial directo («lo que CitaClara le ahorra / le hace visible»). Sin este gráfico la página no cumple su propósito para Sara.

Independent Test: Se puede probar por completo abriendo la página con la semilla v1 y comprobando que aparecen los tres profesionales con su tasa real de no asistencia (no_asistidas entre todas sus citas de la historia) y que los valores cuadran con la historia. Aporta valor por sí sola aunque no existan los demás gráficos.

Acceptance Scenarios:

  1. Given Sara ha accedido con la clave de la clínica y hay historia de 8 semanas, When abre la analítica, Then ve la tasa de no asistencia de cada profesional (María 7,3 %, Jorge 12,6 %, Lucía 10,6 %) en un gráfico de barras con etiquetas en español y sin jerga técnica, y ve también la tasa de la clínica (10,0 %).
  2. Given la semilla v1 regenerada, When se recalcula la tasa, Then los valores son exactamente los mismos (determinismo: misma semilla, misma historia).
  3. Given la página en un móvil (390px), When Sara la muestra en la clínica, Then el gráfico es legible sin desplazamiento horizontal ni manual.

User Story 2 — Ingresos por servicio de citas completadas, al céntimo (Priority: P1)

Sara ve los ingresos por servicio calculados solo con citas completadas, con importes exactos al céntimo, para enseñarle a la clínica el dinero que pasa por la agenda.

Why this priority: Es el segundo argumento de renovación (dinero). La exactitud al céntimo es no negociable (Constitución II); un descuadre de 1 céntimo es un defecto.

Independent Test: Se puede probar por completo sumando precio del servicio × citas completadas por servicio con la semilla v1 y comprobando que cada total y el total global cuadran al céntimo. Aporta valor aunque no existan los gráficos de ocupación o evolución.

Acceptance Scenarios:

  1. Given la historia de la semilla v1, When Sara mira los ingresos por servicio, Then ve exactamente: «sesión fisio» 126 × 40,00 € = 5.040,00 €; «primera visita fisio» 98 × 50,00 € = 4.900,00 €; «consulta nutrición» 58 × 35,00 € = 2.030,00 €; «primera nutrición» 53 × 45,00 € = 2.385,00 €; total 14.355,00 €.
  2. Given cualquier servicio, When se muestra su importe, Then aparece en formato español con dos decimales y símbolo € («5.040,00 €», nunca «5040 €» ni «5040.00€»).
  3. Given que existen citas canceladas y no_asistidas de esos servicios, When se calculan los ingresos, Then esas citas aportan 0,00 € (solo las completadas generan ingreso).

User Story 3 — Ocupación semanal por profesional (Priority: P2)

Sara ve un único número de ocupación semanal por profesional —los minutos de cita completada frente a la jornada disponible en las 8 semanas— para mostrar qué carga real sostiene la agenda.

Why this priority: Completa la foto de actividad junto a la no asistencia y los ingresos, pero es una medida derivada de la agenda y no un argumento de ahorro directo; por eso va después de US1/US2.

Independent Test: Se puede probar por completo comparando el valor mostrado por profesional con el cálculo manual sobre la semilla v1 (minutos de completadas entre 26.400 min de jornada de 8 semanas). Entrega la medida de carga aunque falte la evolución temporal.

Acceptance Scenarios:

  1. Given la semilla v1 y la jornada por defecto 09:00–20:00, When Sara mira la ocupación, Then ve un único porcentaje por profesional que coincide con el cálculo manual: María 6.615/26.400 = 25,1 %, Jorge 4.935/26.400 = 18,7 %, Lucía 4.125/26.400 = 15,6 %, y junto a ellos se indica la jornada usada como denominador (09:00–20:00).
  2. Given la jornada configurable de la clínica (por defecto 09:00–20:00), When se calcula la ocupación, Then el denominador es la jornada vigente (3.300 min/semana por profesional con la jornada por defecto) y el valor mostrado declara qué jornada se usó.
  3. Given las citas cancelada y no_asistida de la historia, When se calcula la ocupación, Then sus minutos no se cuentan como ocupados y por eso la ocupación de la clínica es menor que el total de minutos reservados.

User Story 4 — Evolución de las últimas 8 semanas (Priority: P2)

Sara ve la evolución semana a semana (S-8 a S-1: citas, completadas, no_asistidas, canceladas e ingresos) para mostrar la tendencia de la clínica.

Why this priority: Da el contexto temporal («enseñar lo que CitaClara le ahorra» mes a mes), pero depende de que las métricas base (US1–US3) estén definidas; por eso es P2.

Independent Test: Se puede probar por completo comprobando la fila de cada una de las 8 semanas contra la historia de la semilla v1. Aporta la tendencia aunque la ocupación agregada aún no exista.

Acceptance Scenarios:

  1. Given la semilla v1 cargada en la semana natural del 28/09/2026, When Sara mira la evolución, Then ve estas 8 filas exactas (S-8 la más antigua, S-1 la más reciente):

    Semana Citas Completadas No asistidas Canceladas Ingresos (completadas)
    S-8 48 41 2 5 1.750,00 €
    S-7 46 35 7 4 1.490,00 €
    S-6 56 51 4 1 2.145,00 €
    S-5 49 41 4 4 1.815,00 €
    S-4 53 41 8 4 1.745,00 €
    S-3 59 43 9 7 1.845,00 €
    S-2 49 42 4 3 1.820,00 €
    S-1 49 41 3 5 1.745,00 €
  2. Given las 8 filas, When se suman, Then cuadran con los totales de la historia: 409 citas (335 + 41 + 33) e ingresos 14.355,00 €.
  3. Given las 2 semanas futuras con 82 citas reservadas, When se muestra la evolución, Then esas reservas futuras no se mezclan con las 8 semanas de historia (la evolución es solo historia cerrada).

User Story 5 — Entrar con la misma clave y solo mirar (Priority: P3)

Sara entra a la analítica con la misma clave de panel que la agenda y tiene la garantía de que mirar números nunca cambia nada.

Why this priority: Es la puerta de entrada y la garantía de solo lectura, pero no aporta valor comercial por sí sola; por eso es P3.

Independent Test: Se puede probar por completo entrando con clave correcta e incorrecta, navegando por la página y verificando después que ninguna cita, ficha ni importe ha cambiado. Da confianza aunque no haya gráficos.

Acceptance Scenarios:

  1. Given la pantalla de acceso, When Sara introduce la clave correcta de su clínica, Then entra en la analítica y ve los gráficos.
  2. Given la pantalla de acceso, When introduce una clave incorrecta, Then no entra y ve un aviso claro en español de España, sin revelar información sobre claves válidas.
  3. Given Sara navegando por la analítica (cargar la página, cambiar de gráfico, recargar), When se inspecciona después la base de datos, Then no existe ninguna escritura: cero citas, profesionales, servicios o pacientes creados, modificados o eliminados por esta feature.
  4. Given dos clínicas en la base de datos, When Sara entra con la clave de la suya, Then todas sus cifras corresponden solo a su clínica y ningún nombre ni importe de la otra aparece en pantalla.

Edge Cases

  • ¿Qué pasa si la clínica aún no tiene historia (cero citas en las 8 semanas)? Cada gráfico muestra «Aún no hay datos suficientes» en español, sin divisiones por cero ni errores; los ingresos muestran «0,00 €».
  • ¿Qué pasa si un profesional no tiene citas en el periodo? Aparece con tasa «—» (sin datos) en vez de 0 % engañoso, y su barra no distorsiona la escala de los demás.
  • ¿Qué pasa si un servicio no tiene ninguna cita completada? Aparece con «0,00 €» y 0 citas, sin romperse el desglose.
  • ¿Qué pasa si la jornada de la clínica se reconfigura (no es 09:00–20:00)? La ocupación usa la jornada vigente como denominador; el valor mostrado indica la jornada usada.
  • ¿Qué pasa si un profesional o un servicio se da de baja (activo=false, sin borrado, igual que en la agenda) dentro o después de la ventana? Todos los agregados de la ventana lo siguen contando como si estuviera activo: su tasa, su ocupación y sus ingresos no se recalculan a la baja. Dar de baja a un servicio MUST NOT hacer desaparecer dinero ya facturado ni bajar el total de ingresos de la clínica.
  • ¿Qué pasa si existe más de una clínica en la base de datos? Cada clínica ve exclusivamente sus propias cifras: ningún contador, total, etiqueta ni nombre de otra clínica puede aparecer en su analítica (FR-001). La fuga de datos de otra clínica es un defecto bloqueante.
  • ¿Qué pasa si la clave es correcta pero esa clínica no tiene ninguna cita en la ventana? Se aplica el caso de «sin historia» de más arriba; nunca se recurre a los datos de otra clínica para rellenar el hueco.
  • ¿Qué pasa si se consulta la analítica mientras recepción modifica la agenda? La página muestra una foto coherente (números de una misma lectura); nunca mezcla mitades de dos estados.
  • ¿Qué pasa si los números no cuadran al céntimo en alguna suma? Es un defecto bloqueante (Constitución II): la página no se fusiona hasta que todo cuadre.
  • ¿Qué pasa si se intenta cualquier acción de escritura desde esta página? No existe: no hay botones ni endpoints de escritura en esta feature; cualquier intento se rechaza.

Requirements

Functional Requirements

  • FR-001: La analítica MUST ser una página del panel existente que exige la misma clave de clínica que la agenda (auth simplificada v1 heredada de la 001, sin mecanismo propio nuevo). Con clave incorrecta el acceso MUST denegarse con aviso genérico en español de España. Aislamiento: todo lo que la página calcula y muestra MUST estar acotado a la clínica identificada por la clave con la que se ha entrado; las citas, profesionales, servicios, ingresos, tasas, ocupaciones y totales de cualquier otra clínica MUST NOT aparecer por ningún medio (ni en un contador, ni en un total, ni en un nombre). La agregación MUST NOT ignorar este filtro aunque hoy la base solo contenga una clínica.
  • FR-002 (tasa de no asistencia): La página MUST mostrar por profesional el porcentaje citas no_asistidas / total de citas de ese profesional en las 8 semanas de historia, contando en el denominador las citas completada, no_asistida y cancelada (coherente con el reparto 82/10/8 de la 001; contrato versionado, ver Supuestos). Valores de referencia con la semilla v1: María 11/150 = 7,3 %, Jorge 16/127 = 12,6 %, Lucía 14/132 = 10,6 %, clínica 41/409 = 10,0 %. El porcentaje MUST mostrarse con un decimal y coma decimal («7,3 %»). Las citas reservada futuras MUST NOT entrar en el numerador ni en el denominador. Una reservada con fin ya pasado (cita sin desenlace, ver 001 FR-011) MUST NOT entrar en el numerador ni en el denominador: se reporta aparte como «sin desenlace», nunca se silencia.
  • FR-003 (ocupación semanal): La página MUST mostrar por profesional UN único número: la ocupación semanal de las 8 semanas, calculada como minutos de las citas completadas de ese profesional en la historia / (minutos de jornada × 8 semanas), con una sola cifra decimal. Las citas cancelada y no_asistida MUST NOT contar como minutos ocupados (la primera libera el tramo; la segunda no produjo visita). Una reservada con fin ya pasado (cita sin desenlace, ver 001 FR-011) MUST NOT contar como minutos ocupados y se reporta aparte como «sin desenlace». Con la jornada por defecto 09:00–20:00 y 5 días laborables, la capacidad es 3.300 min/semana y el total de la historia 26.400 min. Valores de referencia con la semilla v1: María 6.615 min = 25,1 %, Jorge 4.935 min = 18,7 %, Lucía 4.125 min = 15,6 %. La ocupación MUST presentarse como valor agregado de las 8 semanas (no como serie semana a semana), mostrando junto a él la jornada usada como denominador.
  • FR-004: La página MUST mostrar los ingresos por servicio calculados exclusivamente con citas completada como n.º de completadas × precio congelado en la cita al reservarla, con importes exactos al céntimo. Fuente del precio: precioCongeladoCentimos de la cita, propiedad de la 001 (001 FR-006, enmienda S-05 aceptada jul2026); esta página solo lo lee. El precio de cada cita MUST ser el que tenía su servicio en el momento de la reserva (copia congelada en la cita), NO el precio actual del servicio: cambiar el precio de un servicio MUST NOT alterar los ingresos de las citas ya cerradas ni reescribir un histórico ya mostrado. Valores de referencia con la semilla v1: sesión fisio 5.040,00 € (126), primera visita fisio 4.900,00 € (98), consulta nutrición 2.030,00 € (58), primera nutrición 2.385,00 € (53), total 14.355,00 €. Las citas cancelada y no_asistida MUST aportar 0,00 €. Las citas de servicios dados de baja (activo=false) MUST seguir contando en sus ingresos: la baja impide reservar, no reescribir lo ya facturado.
  • FR-005: La ventana MUST calcularse en cada carga de la página, no leerse de un valor congelado: la «semana en curso» es la semana natural (lunes a viernes) que contiene la fecha de hoy en Europe/Madrid, y las 8 semanas de historia cerrada son las 8 semanas naturales anteriores (S-8 la más antigua … S-1 la más reciente). La semana en curso MUST NOT entrar en ninguna de las 8 filas porque está incompleta. La página MUST mostrar la evolución de esas 8 semanas con, por semana, citas, completadas, no_asistidas, canceladas e ingresos de completadas. Las citas reservada futuras MUST NOT mezclarse en estas 8 filas.
  • FR-006 (solo lectura): Esta feature MUST NOT escribir nada en tiempo de consulta: no crea, modifica ni elimina citas, profesionales, servicios, pacientes ni clínicas; no expone ningún endpoint de escritura ni botón que mute estado. Toda lectura de agregados MUST calcularse a partir de los datos existentes sin efectos secundarios. El precio congelado de la cita (FR-004) se escribe al reservar por la 001 (001 FR-006, enmienda S-05 aceptada jul2026); esta feature solo lo lee. Aclaración S-07 (jul2026): «solo lectura» significa cero escrituras en consulta; la migración que añade el campo es propiedad de la 001, no de esta página.
  • FR-007 (exactitud — dinero): Todos los importes MUST cuadrar al céntimo: precios y totales se operan en céntimos enteros; si alguna operación futura generase fracción de céntimo, se redondea al céntimo más cercano (mitad hacia arriba). Formato: euros con dos decimales y símbolo € en formato español («5.040,00 €»). Ejemplo límite: 126 × 40,00 € = 5.040,00 € exactos, nunca «5040 €».
  • FR-008 (exactitud — tiempo): Las semanas MUST etiquetarse sin ambigüedad: cada fila de la evolución MUST mostrar su etiqueta S-8 … S-1 junto al rango de días laborables que comprende, en formato día/mes/año (ejemplo para una ventana anclada en la semana natural del 28/09/2026: «S-1: 21/09/2026–25/09/2026», «S-8: 03/08/2026–07/08/2026»; S-8 es la más antigua y S-1 la más reciente). Los rangos MUST derivarse de la ventana calculada en FR-005, nunca de un valor fijo en el código. La jornada usada como denominador de la ocupación (09:00–20:00 por defecto) MUST indicarse visiblemente junto a los porcentajes de ocupación.
  • FR-009 (interfaz): La página MUST ser responsive y funcionar en el portátil de recepción (1440px) y en el móvil (390px), con las CUATRO métricas de la feature —tasa de no asistencia por profesional, ocupación por profesional, ingresos por servicio y evolución de 8 semanas— presentes en la página como gráficos de barras o líneas, no como tablas de texto plano. Cada gráfico MUST tener eje etiquetado y los valores de las barras o puntos legibles junto a ellos. El cuerpo de letra MUST ser de 16 px o más a 390px de ancho. El contraste MUST ser de 4,5:1 o más para el texto y de 3:1 o más para elementos del gráfico (barras, líneas, rejillas). Ningún texto visible MUST usar jerga técnica («dataset», «serie», «KPI», «coeficiente», «métrica», «agregado»): se usan términos de clínica pequeña (fisioterapia, nutrición, podología). Todos los textos MUST estar en español de España.
  • FR-010 (alcance): Quedan fuera y MUST NOT implementarse en esta feature: filtros por rango de fechas elegido por el usuario, comparación entre clínicas, exportación (PDF/CSV), recordatorios, pagos online, predicciones o recomendaciones automáticas, y cualquier escritura.

Key Entities

  • Clínica: Sin cambios respecto a la 001. Solo aporta el ámbito (sus citas) y la clave de acceso.
  • Profesional: Sin cambios. Dimensión de los gráficos de tasa de no asistencia y ocupación (María y Jorge, fisioterapia; Lucía, nutrición).
  • Servicio: Sin cambios. Dimensión de los ingresos (nombre, duración, precio en céntimos como fuente del cálculo).
  • Cita (lectura agregada): En tiempo de ejecución no se crea ni se modifica: la página solo lee y agrega sus datos (profesional, servicio, inicio, estado) y el precio congelado en la reserva (FR-004; campo propiedad de la 001, 001 FR-006), no el precio vigente del servicio. completada genera ingreso; no_asistida alimenta la tasa; cancelada libera tramo y no genera ingreso; reservada futura queda fuera de la evolución de 8 semanas; reservada con fin pasado se reporta como «sin desenlace» (ver 001 FR-011).
  • Esta feature no crea entidades nuevas ni migra nada: el campo de precio congelado en Cita es propiedad de la 001 (migración y escritura de la reserva); esta página nunca lo escribe.

Success Criteria

Measurable Outcomes

  • SC-001: Sara abre la analítica y en menos de 10 segundos distingue por profesional la no asistencia, la ocupación y por servicio los ingresos, sin formación ni manual. Medición: protocolo manual con cronómetro en 1440px y 390px; no es aserción automática de tiempo.
  • SC-002: El 100 % de los importes mostrados cuadra al céntimo con el cálculo manual sobre la semilla v1 (desglose por servicio y total 14.355,00 €; filas semanales de US4). Cero descuadres. Los ingresos de una ventana concreta MUST ser inmóviles aunque la clínica cambie después la tarifa de un servicio (precio congelado en la cita, FR-004).
  • SC-003: Con la misma semilla v1 y la misma ventana (es decir, cargada en la misma semana natural), la página reproduce el 100 % de sus valores (tasas 7,3 / 12,6 / 10,6 %, ocupación 25,1 / 18,7 / 15,6 %, ingresos 14.355,00 € y las 8 filas semanales) bit a bit en el 100 % de las ejecuciones. Regenerar la semilla en una semana natural distinta cambia la ventana y, con ella, los valores esperados: en ese caso los números MUST cuadrar con la historia realmente cargada, no con la tabla de US4.
  • SC-004: Verificación de solo lectura: tras navegar por toda la página y recargar, una comparación de la base de datos antes/después muestra cero escrituras atribuibles a esta feature.
  • SC-005: El 100 % del texto visible de la página (títulos, etiquetas de ejes, mensajes de error, estados vacíos e importes) está en español de España, con formato de fecha día/mes/año e importes «5.040,00 €» y semanas «S-1 … S-8» (reglas de formato en FR-007 y FR-008). La legibilidad y la calidad gráfica se miden en SC-007.
  • SC-006 (aislamiento entre clínicas): Con dos clínicas cargadas en la misma base, la analítica de cada una muestra el 100 % de sus propias cifras y el 0 % de las de la otra: ningún contador, total, etiqueta, nombre de profesional, nombre de servicio ni importe de la segunda clínica aparece en la respuesta de la primera. Verificable con una prueba automática que calcule la analítica de la clínica A sobre una base con datos de A y de B.
  • SC-007 (interfaz medible): Las cuatro métricas de FR-009 se cumplen al 100 % y son comprobables sin subjetividad: 4 de 4 métricas presentes como gráfico con eje y valores, cuerpo de letra ≥ 16 px y sin desplazamiento horizontal a 390px, contraste ≥ 4,5:1 verificado automáticamente en el texto y ≥ 3:1 en los elementos de gráfico, y cero apariciones de jerga técnica en todo el texto visible. Medición: prueba automática de contraste y de palabras prohibidas + revisión visual registrada para el resto.

Assumptions

  • Misma clave de panel que la agenda (auth simplificada v1, deuda consciente heredada de la 001); sin usuarios, roles ni bloqueo tras intentos fallidos en esta feature.
  • Periodo móvil, no congelado: las 8 semanas de historia son las 8 semanas naturales cerradas anteriores a la semana en curso según la fecha de hoy en Europe/Madrid, recalculadas en cada carga de la página; la semana en curso y las 2 semanas futuras de reservas quedan fuera de la evolución; no hay selector de rango en esta feature (ver FR-010).
  • Precios por servicio en céntimos; sin descuentos, impuestos desglosados ni pagos en esta feature. El precio se congela en la cita al reservarla (FR-004), de modo que actualizar la tarifa de un servicio no reescribe los ingresos históricos.
  • Zona horaria Europe/Madrid; formato día/mes/año y 24 h.
  • Definiciones fijadas por Sara el 2026-09-29 (ver Clarifications): tasa de no asistencia = no_asistidas / total de citas del profesional en la historia; ocupación = minutos de completadas / (jornada × 8 semanas), presentada como un único número por profesional.
  • Contrato versionado de métricas (S-08, jul2026): los denominadores (tasa con canceladas dentro; ocupación e ingresos solo con completadas) son parte versionada del contrato. Los oráculos numéricos (409 citas con 335/41/33, 82 futuras, 14.355,00 €, filas S-8…S-1) valen para la semilla v1 (SEMILLA_PRNG=1) cargada en la semana natural del 28/09/2026 y MUST re-generarse si cambian la semilla o la ventana.