From 3ac732b451f7d5a29ac61f3f96ce514a537c1859 Mon Sep 17 00:00:00 2001 From: ArielHS Date: Tue, 3 Mar 2026 00:56:47 +0000 Subject: [PATCH 1/5] feat(metrics): agregar seccion de auditoria y comparacion diaria/mensual - Nueva vista de metricas con comparativas de periodos - Nueva ruta y acceso desde navbar - Template de auditoria con KPIs y tablas diaria/mensual - Ajustes visuales de la interfaz en archivos de estilo y layout - Cobertura de tests para validar calculos y estructura --- pms/statics/css/style.css | 39 +++++++- pms/templates/main.html | 3 + pms/templates/metrics_audit.html | 150 +++++++++++++++++++++++++++++++ pms/tests.py | 62 ++++++++++++- pms/urls.py | 3 +- pms/views.py | 120 +++++++++++++++++++++++++ 6 files changed, 374 insertions(+), 3 deletions(-) create mode 100644 pms/templates/metrics_audit.html diff --git a/pms/statics/css/style.css b/pms/statics/css/style.css index 91d9999a8..8b7bceca3 100644 --- a/pms/statics/css/style.css +++ b/pms/statics/css/style.css @@ -38,4 +38,41 @@ body{ justify-content: center; height: 100%; align-items: center; -} \ No newline at end of file +} +.metrics-grid{ + display: grid; + gap: 0.9rem; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); +} + +.metrics-value{ + font-size: 1.8rem; + font-weight: 700; + line-height: 1.15; +} + +.metrics-delta{ + font-size: 0.9rem; + font-weight: 600; + margin-top: 0.35rem; +} + +.metrics-up{ + color: #0f766e; +} + +.metrics-down{ + color: #c2410c; +} + +.metrics-flat{ + color: #6c757d; +} + +.metrics-compact{ + background: #f5f5f5; + border: 1px solid #e7e7e7; + border-radius: 12px; + padding: 0.75rem; + height: 100%; +} diff --git a/pms/templates/main.html b/pms/templates/main.html index b2216a759..389356993 100644 --- a/pms/templates/main.html +++ b/pms/templates/main.html @@ -34,6 +34,9 @@ +
diff --git a/pms/templates/metrics_audit.html b/pms/templates/metrics_audit.html new file mode 100644 index 000000000..c1f8c9566 --- /dev/null +++ b/pms/templates/metrics_audit.html @@ -0,0 +1,150 @@ +{% extends "main.html"%} + +{% block content %} +
+
+

Métricas y Auditoría

+

Monitoreo diario y mensual con comparación de períodos y trazabilidad operativa.

+
+ +
+
+
Reservas creadas hoy
+
{{ daily_current.created_count }}
+
+ {% if daily_comparison.created_count.direction == "up" %}▲{% elif daily_comparison.created_count.direction == "down" %}▼{% else %}■{% endif %} + {{ daily_comparison.created_count.percent|floatformat:1 }}% vs ayer +
+
+
+
Confirmadas hoy
+
{{ daily_current.confirmed_count }}
+
+ {% if daily_comparison.confirmed_count.direction == "up" %}▲{% elif daily_comparison.confirmed_count.direction == "down" %}▼{% else %}■{% endif %} + {{ daily_comparison.confirmed_count.percent|floatformat:1 }}% vs ayer +
+
+
+
Canceladas hoy
+
{{ daily_current.cancelled_count }}
+
+ {% if daily_comparison.cancelled_count.direction == "up" %}▲{% elif daily_comparison.cancelled_count.direction == "down" %}▼{% else %}■{% endif %} + {{ daily_comparison.cancelled_count.percent|floatformat:1 }}% vs ayer +
+
+
+
Facturación hoy
+
€ {{ daily_current.revenue|floatformat:2 }}
+
+ {% if daily_comparison.revenue.direction == "up" %}▲{% elif daily_comparison.revenue.direction == "down" %}▼{% else %}■{% endif %} + {{ daily_comparison.revenue.percent|floatformat:1 }}% vs ayer +
+
+
+ +
+
+
+
+
Auditoría Diaria (últimos 7 días)
+
+ + + + + + + + + + + {% for row in daily_audit %} + + + + + + + {% endfor %} + +
DíaCreadasCanceladasFacturación
{{ row.label }}{{ row.created_count }}{{ row.cancelled_count }}€ {{ row.revenue|floatformat:2 }}
+
+
+
+
+
+
+
+
Auditoría Mensual (últimos 6 meses)
+
+ + + + + + + + + + + {% for row in monthly_audit %} + + + + + + + {% endfor %} + +
MesCreadasCanceladasFacturación
{{ row.label }}{{ row.created_count }}{{ row.cancelled_count }}€ {{ row.revenue|floatformat:2 }}
+
+
+
+
+
+ +
+
+
Comparación Mensual
+
+
+
+
Reservas creadas
+
{{ monthly_current.created_count }}
+
+ {{ monthly_comparison.created_count.percent|floatformat:1 }}% vs mes anterior +
+
+
+
+
+
Confirmadas
+
{{ monthly_current.confirmed_count }}
+
+ {{ monthly_comparison.confirmed_count.percent|floatformat:1 }}% vs mes anterior +
+
+
+
+
+
Canceladas
+
{{ monthly_current.cancelled_count }}
+
+ {{ monthly_comparison.cancelled_count.percent|floatformat:1 }}% vs mes anterior +
+
+
+
+
+
Facturación
+
€ {{ monthly_current.revenue|floatformat:2 }}
+
+ {{ monthly_comparison.revenue.percent|floatformat:1 }}% vs mes anterior +
+
+
+
+
+
+
+{% endblock content%} diff --git a/pms/tests.py b/pms/tests.py index 7ce503c2d..517cc0397 100644 --- a/pms/tests.py +++ b/pms/tests.py @@ -1,3 +1,63 @@ +from datetime import date, datetime, time, timedelta + from django.test import TestCase +from django.test.utils import override_settings +from django.urls import reverse + +from .models import Booking, Room, Room_type + + +@override_settings(STATICFILES_STORAGE="django.contrib.staticfiles.storage.StaticFilesStorage") +class MetricsAuditTests(TestCase): + @classmethod + def setUpTestData(cls): + room_type = Room_type.objects.create(name="Simple", price=20, max_guests=1) + cls.room = Room.objects.create(room_type=room_type, name="Room 3.1", description="Desc") + cls.today = date.today() + cls.yesterday = cls.today - timedelta(days=1) + cls.month_start = cls.today.replace(day=1) + cls.prev_month_end = cls.month_start - timedelta(days=1) + cls.prev_month_start = cls.prev_month_end.replace(day=1) + + cls._create_booking("NEW", 100, datetime.combine(cls.today, time(11, 0)), "MTA00001") + cls._create_booking("DEL", 120, datetime.combine(cls.today, time(12, 0)), "MTA00002") + cls._create_booking("NEW", 60, datetime.combine(cls.yesterday, time(10, 0)), "MTA00003") + cls._create_booking("NEW", 40, datetime.combine(cls.prev_month_start + timedelta(days=2), time(10, 0)), "MTA00004") + cls._create_booking("DEL", 35, datetime.combine(cls.prev_month_start + timedelta(days=3), time(10, 0)), "MTA00005") + + @classmethod + def _create_booking(cls, state, total, created_at, code): + booking = Booking.objects.create( + state=state, + checkin=created_at.date(), + checkout=created_at.date() + timedelta(days=1), + room=cls.room, + guests=1, + total=total, + code=code, + ) + Booking.objects.filter(id=booking.id).update(created=created_at) + + def test_metrics_audit_view_renders_and_contains_sections(self): + response = self.client.get(reverse("metrics_audit")) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Métricas y Auditoría") + self.assertContains(response, "Auditoría Diaria") + self.assertContains(response, "Auditoría Mensual") + self.assertContains(response, "Comparación Mensual") + + def test_metrics_audit_daily_metrics_values(self): + response = self.client.get(reverse("metrics_audit")) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context["daily_current"]["created_count"], 2) + self.assertEqual(response.context["daily_current"]["confirmed_count"], 1) + self.assertEqual(response.context["daily_current"]["cancelled_count"], 1) + self.assertEqual(response.context["daily_current"]["revenue"], 100.0) + self.assertEqual(response.context["daily_previous"]["created_count"], 1) + self.assertEqual(response.context["daily_previous"]["revenue"], 60.0) -# Create your tests here. + def test_metrics_audit_generates_expected_audit_rows(self): + response = self.client.get(reverse("metrics_audit")) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context["daily_audit"]), 7) + self.assertEqual(len(response.context["monthly_audit"]), 6) diff --git a/pms/urls.py b/pms/urls.py index c18714abf..359feffd4 100644 --- a/pms/urls.py +++ b/pms/urls.py @@ -11,5 +11,6 @@ path("booking//delete", views.DeleteBookingView.as_view(), name="delete_booking"), path("rooms/", views.RoomsView.as_view(), name="rooms"), path("room//", views.RoomDetailsView.as_view(), name="room_details"), - path("dashboard/", views.DashboardView.as_view(), name="dashboard") + path("dashboard/", views.DashboardView.as_view(), name="dashboard"), + path("metrics/", views.MetricsAuditView.as_view(), name="metrics_audit"), ] diff --git a/pms/views.py b/pms/views.py index f38563933..9cf84e92f 100644 --- a/pms/views.py +++ b/pms/views.py @@ -1,4 +1,7 @@ +from datetime import date, timedelta + from django.db.models import F, Q, Count, Sum +from django.db.models.functions import TruncDate, TruncMonth from django.shortcuts import render, redirect from django.utils.decorators import method_decorator from django.views import View @@ -244,3 +247,120 @@ def get(self, request): 'rooms': rooms } return render(request, "rooms.html", context) + + +class MetricsAuditView(View): + @staticmethod + def _period_metrics(start_date, end_date): + bookings = Booking.objects.filter(created__date__gte=start_date, created__date__lte=end_date) + confirmed = bookings.exclude(state="DEL") + return { + "created_count": bookings.count(), + "confirmed_count": confirmed.count(), + "cancelled_count": bookings.filter(state="DEL").count(), + "revenue": float(confirmed.aggregate(total=Sum("total"))["total"] or 0), + } + + @staticmethod + def _comparison(current, previous): + delta = current - previous + if previous == 0: + percent = 0 if current == 0 else 100 + else: + percent = (delta / previous) * 100 + direction = "up" if delta > 0 else "down" if delta < 0 else "flat" + return { + "delta": delta, + "percent": percent, + "direction": direction, + } + + def get(self, request): + today = date.today() + yesterday = today - timedelta(days=1) + month_start = today.replace(day=1) + previous_month_end = month_start - timedelta(days=1) + previous_month_start = previous_month_end.replace(day=1) + + daily_current = self._period_metrics(today, today) + daily_previous = self._period_metrics(yesterday, yesterday) + monthly_current = self._period_metrics(month_start, today) + monthly_previous = self._period_metrics(previous_month_start, previous_month_end) + + daily_comparison = { + "created_count": self._comparison(daily_current["created_count"], daily_previous["created_count"]), + "confirmed_count": self._comparison(daily_current["confirmed_count"], daily_previous["confirmed_count"]), + "cancelled_count": self._comparison(daily_current["cancelled_count"], daily_previous["cancelled_count"]), + "revenue": self._comparison(daily_current["revenue"], daily_previous["revenue"]), + } + monthly_comparison = { + "created_count": self._comparison(monthly_current["created_count"], monthly_previous["created_count"]), + "confirmed_count": self._comparison(monthly_current["confirmed_count"], monthly_previous["confirmed_count"]), + "cancelled_count": self._comparison(monthly_current["cancelled_count"], monthly_previous["cancelled_count"]), + "revenue": self._comparison(monthly_current["revenue"], monthly_previous["revenue"]), + } + + last_7_days_start = today - timedelta(days=6) + raw_daily = (Booking.objects + .filter(created__date__gte=last_7_days_start, created__date__lte=today) + .annotate(period=TruncDate("created")) + .values("period") + .annotate( + created_count=Count("id"), + cancelled_count=Count("id", filter=Q(state="DEL")), + revenue=Sum("total", filter=~Q(state="DEL")), + ) + .order_by("period")) + daily_map = {entry["period"]: entry for entry in raw_daily} + daily_audit = [] + for day_offset in range(6, -1, -1): + day = today - timedelta(days=day_offset) + entry = daily_map.get(day, {}) + daily_audit.append({ + "label": day.strftime("%d/%m"), + "created_count": entry.get("created_count", 0), + "cancelled_count": entry.get("cancelled_count", 0), + "revenue": float(entry.get("revenue") or 0), + }) + + month_cursor = month_start + for _ in range(5): + month_cursor = (month_cursor - timedelta(days=1)).replace(day=1) + raw_monthly = (Booking.objects + .filter(created__date__gte=month_cursor, created__date__lte=today) + .annotate(period=TruncMonth("created")) + .values("period") + .annotate( + created_count=Count("id"), + cancelled_count=Count("id", filter=Q(state="DEL")), + revenue=Sum("total", filter=~Q(state="DEL")), + ) + .order_by("period")) + monthly_map = { + (entry["period"].year, entry["period"].month): entry for entry in raw_monthly + } + monthly_audit = [] + month_iter = month_cursor + for _ in range(6): + key = (month_iter.year, month_iter.month) + entry = monthly_map.get(key, {}) + monthly_audit.append({ + "label": month_iter.strftime("%b %Y"), + "created_count": entry.get("created_count", 0), + "cancelled_count": entry.get("cancelled_count", 0), + "revenue": float(entry.get("revenue") or 0), + }) + month_iter = (month_iter + timedelta(days=32)).replace(day=1) + + context = { + "daily_current": daily_current, + "daily_previous": daily_previous, + "monthly_current": monthly_current, + "monthly_previous": monthly_previous, + "daily_comparison": daily_comparison, + "monthly_comparison": monthly_comparison, + "daily_audit": daily_audit, + "monthly_audit": monthly_audit, + "today": today, + } + return render(request, "metrics_audit.html", context) From e95223d5b873d86861c8b24fa43231edf585fd7d Mon Sep 17 00:00:00 2001 From: ArielHS Date: Tue, 3 Mar 2026 11:25:01 +0000 Subject: [PATCH 2/5] =?UTF-8?q?feat(auditoria):=20redise=C3=B1a=20la=20sec?= =?UTF-8?q?ci=C3=B3n=20de=20m=C3=A9tricas=20en=20auditor=C3=ADa=20operativ?= =?UTF-8?q?a=20con=20UX=20configurable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resumen ejecutivo: - Renombra el acceso de navegación de 'Métricas' a 'Auditoría' para alinear semántica de negocio. - Transforma la pantalla en un tablero de auditoría con foco en lectura operativa diaria y mensual. Mejoras funcionales implementadas: - Nuevos KPIs derivados: tasa de cancelación, ticket promedio, picos de facturación y acumulados por ventana de análisis. - Controles de visualización: vista diaria, mensual y combinada; con widgets activables (tabla, barras y resumen ejecutivo). - Visualizaciones de tendencia mediante barras normalizadas para reservas y facturación, sin dependencias externas. Mejoras técnicas: - Extensión de MetricsAuditView para calcular métricas comparativas y porcentajes listos para UI. - Ajuste de estilos responsive para mantener legibilidad y jerarquía visual en desktop/móvil. - Actualización de pruebas de la vista para reflejar el nuevo contenido renderizado. Documentación incluida: - docs/pr-5-metrics-audit.md con alcance, archivos tocados, pruebas y valor esperado para PM/Arquitectura. --- docs/pr-5-metrics-audit.md | 58 +++++ pms/statics/css/style.css | 104 +++++++++ pms/templates/main.html | 4 +- pms/templates/metrics_audit.html | 361 ++++++++++++++++++++++++------- pms/tests.py | 4 +- pms/views.py | 35 +++ 6 files changed, 478 insertions(+), 88 deletions(-) create mode 100644 docs/pr-5-metrics-audit.md diff --git a/docs/pr-5-metrics-audit.md b/docs/pr-5-metrics-audit.md new file mode 100644 index 000000000..f0256ff43 --- /dev/null +++ b/docs/pr-5-metrics-audit.md @@ -0,0 +1,58 @@ +# PR 5 - Auditoria Operativa y Metricas Comparativas + +## Objetivo +Elevar la seccion de metricas a una experiencia de auditoria operativa util para negocio y liderazgo tecnico, permitiendo analisis diario/mensual, lectura rapida de tendencias y comparacion entre periodos sin salir del flujo principal. + +## Alcance implementado +- Renombre funcional en navegacion: `Metricas` -> `Auditoria`. +- Rediseno de la seccion hacia un layout ejecutivo, con: + - Cabecera de contexto operativo (fecha de corte). + - KPIs principales del dia con variacion porcentual. + - Vistas por periodo (diaria, mensual y combinada). + - Widgets configurables para tabla, barras y resumen ejecutivo. +- Nuevos indicadores procesados en backend: + - Tasa de cancelacion (diaria y mensual). + - Ticket promedio (diario y mensual). + - Pico de facturacion (dia y mes). + - Acumulados de facturacion para ventanas de analisis. +- Visualizacion comparativa sin librerias externas: + - Barras normalizadas para reservas creadas y facturacion. + - Lectura rapida para detectar picos/caidas. +- Ajuste de pruebas para reflejar la nueva narrativa de pantalla. + +## Archivos modificados +- `pms/templates/main.html` +- `pms/templates/metrics_audit.html` +- `pms/views.py` +- `pms/statics/css/style.css` +- `pms/tests.py` + +## Detalle tecnico por componente +### Navegacion +Se actualiza el label del item en toolbar a `Auditoria`, alineado con el objetivo de negocio de la funcionalidad. + +### Backend (procesamiento) +La vista `MetricsAuditView` incorpora calculos adicionales para enriquecer la toma de decisiones: +- porcentajes normalizados para visualizaciones de barras, +- metricas derivadas (rate/avg/peak), +- agregados diarios y mensuales listos para presentacion. + +### Frontend (UX/UI) +Se implementa una interfaz mas orientada a analitica operativa: +- controles de vista por periodo, +- selector de widgets visibles, +- bloques de resumen ejecutivo, +- tablas y barras para analisis comparativo, +- comportamiento responsive para desktop y mobile. + +### Calidad y mantenibilidad +La solucion reutiliza datos existentes del dominio `Booking` y evita dependencias adicionales, manteniendo bajo costo de mantenimiento y facil evolucion futura. + +## Pruebas ejecutadas +- `python manage.py test pms.tests.MetricsAuditTests` +- Resultado: **OK (3/3)** + +## Impacto esperado +- Mayor velocidad para identificar cambios de comportamiento operativo. +- Mejor soporte para conversaciones de negocio (ingresos, cancelacion, tendencia). +- Base solida para evolucionar a widgets avanzados (forecast, cohortes, alertas). diff --git a/pms/statics/css/style.css b/pms/statics/css/style.css index 8b7bceca3..b6a311dee 100644 --- a/pms/statics/css/style.css +++ b/pms/statics/css/style.css @@ -76,3 +76,107 @@ body{ padding: 0.75rem; height: 100%; } + +.audit-page{ + padding-bottom: 2rem; +} + +.audit-hero{ + display: flex; + justify-content: space-between; + gap: 1rem; + align-items: flex-start; +} + +.audit-period-badge{ + border: 1px solid #d8e0e8; + border-radius: 999px; + padding: 0.35rem 0.75rem; + font-size: 0.78rem; + font-weight: 600; + color: #425466; + background: #f7f9fc; +} + +.audit-toolbar .btn{ + border-radius: 999px; +} + +.audit-toggle-btn.is-active, +.audit-widget-btn.is-active{ + background: #1f4a6d; + border-color: #1f4a6d; + color: #fff; +} + +.audit-mini-title{ + font-size: 0.8rem; + color: #586573; + font-weight: 600; +} + +.audit-mini-value{ + font-size: 1.45rem; + font-weight: 700; + color: #1f2a36; + margin-top: 0.3rem; +} + +.audit-mini-sub{ + margin-top: 0.2rem; + color: #7a8794; + font-size: 0.8rem; +} + +.audit-bar-row{ + display: grid; + grid-template-columns: 90px 1fr auto; + gap: 0.6rem; + align-items: center; + margin-bottom: 0.4rem; +} + +.audit-bar-label{ + color: #5f6b76; + font-size: 0.78rem; + font-weight: 600; +} + +.audit-bar-track{ + width: 100%; + height: 10px; + border-radius: 999px; + background: #edf1f5; + overflow: hidden; +} + +.audit-bar{ + height: 10px; + border-radius: 999px; +} + +.audit-bar-created{ + background: linear-gradient(90deg, #1f4a6d, #2f6f9f); +} + +.audit-bar-revenue{ + background: linear-gradient(90deg, #2c6b2f, #49a356); +} + +.audit-bar-number{ + font-size: 0.78rem; + color: #4d5965; + font-weight: 600; + min-width: 84px; + text-align: right; +} + +@media (max-width: 768px){ + .audit-hero{ + flex-direction: column; + } + + .audit-bar-row{ + grid-template-columns: 68px 1fr auto; + } +} diff --git a/pms/templates/main.html b/pms/templates/main.html index 389356993..7a4ed3386 100644 --- a/pms/templates/main.html +++ b/pms/templates/main.html @@ -35,7 +35,7 @@ Habitaciones @@ -52,4 +52,4 @@ {% endblock %} - \ No newline at end of file + diff --git a/pms/templates/metrics_audit.html b/pms/templates/metrics_audit.html index c1f8c9566..7c40038b3 100644 --- a/pms/templates/metrics_audit.html +++ b/pms/templates/metrics_audit.html @@ -1,10 +1,28 @@ {% extends "main.html"%} {% block content %} -
-
-

Métricas y Auditoría

-

Monitoreo diario y mensual con comparación de períodos y trazabilidad operativa.

+
+
+
+

Auditoría Operativa

+

Indicadores diarios y mensuales para seguimiento comercial, control de cancelaciones y comparación de desempeño.

+
+
Corte: {{ today|date:"d/m/Y" }}
+
+ +
+
+
+ + + +
+
+ + + +
+
@@ -42,104 +60,239 @@
Facturación hoy
-
-
-
-
-
Auditoría Diaria (últimos 7 días)
-
- - - - - - - - - - - {% for row in daily_audit %} - - - - - - - {% endfor %} - -
DíaCreadasCanceladasFacturación
{{ row.label }}{{ row.created_count }}{{ row.cancelled_count }}€ {{ row.revenue|floatformat:2 }}
+
+
+
+
+
+
Cancelación 7 días
+
{{ daily_cancellation_rate|floatformat:1 }}%
+
Base: {{ daily_current.created_count }} creadas hoy
+
+
+
+
+
+
+
Ticket promedio diario
+
€ {{ daily_avg_ticket|floatformat:2 }}
+
Solo reservas confirmadas
+
+
+
+
+
+
+
Pico facturación (día)
+
{{ peak_day.label }}
+
€ {{ peak_day.revenue|floatformat:2 }}
+
+
+
+
+
+
+
Facturación 7 días
+
€ {{ total_daily_revenue|floatformat:2 }}
+
Acumulado operativo
-
-
-
-
Auditoría Mensual (últimos 6 meses)
-
- - - - - - - - - - - {% for row in monthly_audit %} - - - - - - - {% endfor %} - -
MesCreadasCanceladasFacturación
{{ row.label }}{{ row.created_count }}{{ row.cancelled_count }}€ {{ row.revenue|floatformat:2 }}
+ +
+
+
+
+
Auditoría Diaria (últimos 7 días)
+
+ + + + + + + + + + + {% for row in daily_audit %} + + + + + + + {% endfor %} + +
DíaCreadasCanceladasFacturación
{{ row.label }}{{ row.created_count }}{{ row.cancelled_count }}€ {{ row.revenue|floatformat:2 }}
+
+
+
+
+ +
+
+
+
Tendencia diaria (barras normalizadas)
+ {% for row in daily_audit %} +
+
{{ row.label }}
+
+
+
+
{{ row.created_count }}
+
+
+
 
+
+
+
+
€ {{ row.revenue|floatformat:0 }}
+
+ {% endfor %}
-
-
-
Comparación Mensual
-
-
-
-
Reservas creadas
-
{{ monthly_current.created_count }}
-
- {{ monthly_comparison.created_count.percent|floatformat:1 }}% vs mes anterior -
+
+
+
+
+
+
Cancelación mensual
+
{{ monthly_cancellation_rate|floatformat:1 }}%
+
Ventana 6 meses
+
+
+
+
+
+
+
Ticket promedio mensual
+
€ {{ monthly_avg_ticket|floatformat:2 }}
+
Mes en curso
+
+
+
+
+
+
+
Pico facturación (mes)
+
{{ peak_month.label }}
+
€ {{ peak_month.revenue|floatformat:2 }}
+
+
+
+
+
+
+
Facturación 6 meses
+
€ {{ total_monthly_revenue|floatformat:2 }}
+
Acumulado estratégico
-
-
-
Confirmadas
-
{{ monthly_current.confirmed_count }}
-
- {{ monthly_comparison.confirmed_count.percent|floatformat:1 }}% vs mes anterior +
+
+ +
+
+
+
+
Auditoría Mensual (últimos 6 meses)
+
+ + + + + + + + + + + {% for row in monthly_audit %} + + + + + + + {% endfor %} + +
MesCreadasCanceladasFacturación
{{ row.label }}{{ row.created_count }}{{ row.cancelled_count }}€ {{ row.revenue|floatformat:2 }}
-
-
-
Canceladas
-
{{ monthly_current.cancelled_count }}
-
- {{ monthly_comparison.cancelled_count.percent|floatformat:1 }}% vs mes anterior +
+ +
+
+
+
Tendencia mensual (barras normalizadas)
+ {% for row in monthly_audit %} +
+
{{ row.label }}
+
+
+
+
{{ row.created_count }}
+
+
+
 
+
+
+
+
€ {{ row.revenue|floatformat:0 }}
+ {% endfor %}
-
-
-
Facturación
-
€ {{ monthly_current.revenue|floatformat:2 }}
-
- {{ monthly_comparison.revenue.percent|floatformat:1 }}% vs mes anterior +
+
+
+ +
+
+
+
Comparación mensual consolidada
+
+
+
+
Reservas creadas
+
{{ monthly_current.created_count }}
+
+ {{ monthly_comparison.created_count.percent|floatformat:1 }}% vs mes anterior +
+
+
+
+
+
Confirmadas
+
{{ monthly_current.confirmed_count }}
+
+ {{ monthly_comparison.confirmed_count.percent|floatformat:1 }}% vs mes anterior +
+
+
+
+
+
Canceladas
+
{{ monthly_current.cancelled_count }}
+
+ {{ monthly_comparison.cancelled_count.percent|floatformat:1 }}% vs mes anterior +
+
+
+
+
+
Facturación
+
€ {{ monthly_current.revenue|floatformat:2 }}
+
+ {{ monthly_comparison.revenue.percent|floatformat:1 }}% vs mes anterior +
@@ -147,4 +300,44 @@
Comparación Mensual
+ + {% endblock content%} diff --git a/pms/tests.py b/pms/tests.py index 517cc0397..4d5b17940 100644 --- a/pms/tests.py +++ b/pms/tests.py @@ -41,10 +41,10 @@ def _create_booking(cls, state, total, created_at, code): def test_metrics_audit_view_renders_and_contains_sections(self): response = self.client.get(reverse("metrics_audit")) self.assertEqual(response.status_code, 200) - self.assertContains(response, "Métricas y Auditoría") + self.assertContains(response, "Auditoría Operativa") self.assertContains(response, "Auditoría Diaria") self.assertContains(response, "Auditoría Mensual") - self.assertContains(response, "Comparación Mensual") + self.assertContains(response, "Comparación mensual consolidada") def test_metrics_audit_daily_metrics_values(self): response = self.client.get(reverse("metrics_audit")) diff --git a/pms/views.py b/pms/views.py index 9cf84e92f..591d1037a 100644 --- a/pms/views.py +++ b/pms/views.py @@ -352,6 +352,33 @@ def get(self, request): }) month_iter = (month_iter + timedelta(days=32)).replace(day=1) + daily_max_created = max((item["created_count"] for item in daily_audit), default=0) + daily_max_revenue = max((item["revenue"] for item in daily_audit), default=0) + for item in daily_audit: + item["created_pct"] = 0 if daily_max_created == 0 else int((item["created_count"] / daily_max_created) * 100) + item["revenue_pct"] = 0 if daily_max_revenue == 0 else int((item["revenue"] / daily_max_revenue) * 100) + + monthly_max_created = max((item["created_count"] for item in monthly_audit), default=0) + monthly_max_revenue = max((item["revenue"] for item in monthly_audit), default=0) + for item in monthly_audit: + item["created_pct"] = 0 if monthly_max_created == 0 else int((item["created_count"] / monthly_max_created) * 100) + item["revenue_pct"] = 0 if monthly_max_revenue == 0 else int((item["revenue"] / monthly_max_revenue) * 100) + + total_daily_created = sum(item["created_count"] for item in daily_audit) + total_daily_cancelled = sum(item["cancelled_count"] for item in daily_audit) + total_daily_revenue = sum(item["revenue"] for item in daily_audit) + total_monthly_created = sum(item["created_count"] for item in monthly_audit) + total_monthly_cancelled = sum(item["cancelled_count"] for item in monthly_audit) + total_monthly_revenue = sum(item["revenue"] for item in monthly_audit) + + daily_cancellation_rate = 0 if total_daily_created == 0 else (total_daily_cancelled / total_daily_created) * 100 + monthly_cancellation_rate = 0 if total_monthly_created == 0 else (total_monthly_cancelled / total_monthly_created) * 100 + daily_avg_ticket = 0 if daily_current["confirmed_count"] == 0 else daily_current["revenue"] / daily_current["confirmed_count"] + monthly_avg_ticket = 0 if monthly_current["confirmed_count"] == 0 else monthly_current["revenue"] / monthly_current["confirmed_count"] + + peak_day = max(daily_audit, key=lambda x: x["revenue"], default={"label": "-", "revenue": 0}) + peak_month = max(monthly_audit, key=lambda x: x["revenue"], default={"label": "-", "revenue": 0}) + context = { "daily_current": daily_current, "daily_previous": daily_previous, @@ -361,6 +388,14 @@ def get(self, request): "monthly_comparison": monthly_comparison, "daily_audit": daily_audit, "monthly_audit": monthly_audit, + "daily_cancellation_rate": daily_cancellation_rate, + "monthly_cancellation_rate": monthly_cancellation_rate, + "daily_avg_ticket": daily_avg_ticket, + "monthly_avg_ticket": monthly_avg_ticket, + "total_daily_revenue": total_daily_revenue, + "total_monthly_revenue": total_monthly_revenue, + "peak_day": peak_day, + "peak_month": peak_month, "today": today, } return render(request, "metrics_audit.html", context) From 8caf7e3e50c09e4d27511705c717cdc2279761f7 Mon Sep 17 00:00:00 2001 From: ArielHS Date: Tue, 3 Mar 2026 11:32:02 +0000 Subject: [PATCH 3/5] docs(pr-5): add proposed next iteration section in English --- docs/pr-5-metrics-audit.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/pr-5-metrics-audit.md b/docs/pr-5-metrics-audit.md index f0256ff43..ee3ca7cf6 100644 --- a/docs/pr-5-metrics-audit.md +++ b/docs/pr-5-metrics-audit.md @@ -56,3 +56,10 @@ La solucion reutiliza datos existentes del dominio `Booking` y evita dependencia - Mayor velocidad para identificar cambios de comportamiento operativo. - Mejor soporte para conversaciones de negocio (ingresos, cancelacion, tendencia). - Base solida para evolucionar a widgets avanzados (forecast, cohortes, alertas). + +## Proposed Next Iteration (English) +- Add date-range presets (`Today`, `Last 7 days`, `Month to date`, `Last 90 days`) with URL persistence for shareable views. +- Include room-type segmentation to compare demand and cancellation behavior across categories. +- Add threshold-based alerts (for example: cancellation rate > X%) to surface risks proactively. +- Provide CSV export for table datasets to support external analysis and stakeholder reporting. +- Introduce trend forecasting (simple moving average) to estimate short-term bookings and revenue. From cc9b3e5ae5e7fb29fff870182a7580dee9e8a696 Mon Sep 17 00:00:00 2001 From: ArielHS Date: Tue, 3 Mar 2026 11:37:26 +0000 Subject: [PATCH 4/5] docs(pr-5): remove unintended English section from audit document --- docs/pr-5-metrics-audit.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docs/pr-5-metrics-audit.md b/docs/pr-5-metrics-audit.md index ee3ca7cf6..f0256ff43 100644 --- a/docs/pr-5-metrics-audit.md +++ b/docs/pr-5-metrics-audit.md @@ -56,10 +56,3 @@ La solucion reutiliza datos existentes del dominio `Booking` y evita dependencia - Mayor velocidad para identificar cambios de comportamiento operativo. - Mejor soporte para conversaciones de negocio (ingresos, cancelacion, tendencia). - Base solida para evolucionar a widgets avanzados (forecast, cohortes, alertas). - -## Proposed Next Iteration (English) -- Add date-range presets (`Today`, `Last 7 days`, `Month to date`, `Last 90 days`) with URL persistence for shareable views. -- Include room-type segmentation to compare demand and cancellation behavior across categories. -- Add threshold-based alerts (for example: cancellation rate > X%) to surface risks proactively. -- Provide CSV export for table datasets to support external analysis and stakeholder reporting. -- Introduce trend forecasting (simple moving average) to estimate short-term bookings and revenue. From c959f8da9f0868b9a7f16ed78344e20776d520b9 Mon Sep 17 00:00:00 2001 From: ArielHS Date: Tue, 3 Mar 2026 11:52:57 +0000 Subject: [PATCH 5/5] fix(ui-auditoria): corrige contraste y legibilidad en modo oscuro/claro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ajusta badges, botones de toolbar, tablas, KPIs y barras de la vista de Auditoría para usar variables de tema y mejorar accesibilidad visual. --- pms/statics/css/style.css | 78 +++++++++++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 11 deletions(-) diff --git a/pms/statics/css/style.css b/pms/statics/css/style.css index b6a311dee..e286a998c 100644 --- a/pms/statics/css/style.css +++ b/pms/statics/css/style.css @@ -89,42 +89,53 @@ body{ } .audit-period-badge{ - border: 1px solid #d8e0e8; + border: 1px solid var(--app-border); border-radius: 999px; padding: 0.35rem 0.75rem; font-size: 0.78rem; font-weight: 600; - color: #425466; - background: #f7f9fc; + color: var(--app-muted); + background: var(--app-surface); } .audit-toolbar .btn{ border-radius: 999px; } +.audit-toolbar .btn-outline-secondary{ + color: var(--app-text); + border-color: var(--app-border); +} + +.audit-toolbar .btn-outline-secondary:hover{ + color: var(--app-text); + border-color: var(--app-border); + background: var(--app-hover); +} + .audit-toggle-btn.is-active, .audit-widget-btn.is-active{ - background: #1f4a6d; - border-color: #1f4a6d; + background: var(--app-primary); + border-color: var(--app-primary); color: #fff; } .audit-mini-title{ font-size: 0.8rem; - color: #586573; + color: var(--app-muted); font-weight: 600; } .audit-mini-value{ font-size: 1.45rem; font-weight: 700; - color: #1f2a36; + color: var(--app-text); margin-top: 0.3rem; } .audit-mini-sub{ margin-top: 0.2rem; - color: #7a8794; + color: var(--app-muted); font-size: 0.8rem; } @@ -137,7 +148,7 @@ body{ } .audit-bar-label{ - color: #5f6b76; + color: var(--app-muted); font-size: 0.78rem; font-weight: 600; } @@ -146,7 +157,7 @@ body{ width: 100%; height: 10px; border-radius: 999px; - background: #edf1f5; + background: var(--app-hover); overflow: hidden; } @@ -165,12 +176,57 @@ body{ .audit-bar-number{ font-size: 0.78rem; - color: #4d5965; + color: var(--app-muted); font-weight: 600; min-width: 84px; text-align: right; } +.audit-page .table{ + color: var(--app-text); +} + +.audit-page .table thead th{ + color: var(--app-muted); + font-weight: 700; +} + +.audit-page .table > :not(caption) > * > *{ + border-color: var(--app-border); +} + +[data-theme="dark"] .audit-period-badge{ + background: #111827; + border-color: #334155; + color: #cbd5e1; +} + +[data-theme="dark"] .audit-toolbar .btn-outline-secondary{ + color: #cbd5e1; + border-color: #475569; +} + +[data-theme="dark"] .audit-toolbar .btn-outline-secondary:hover{ + color: #e2e8f0; + border-color: #64748b; + background: #334155; +} + +[data-theme="dark"] .audit-toggle-btn.is-active, +[data-theme="dark"] .audit-widget-btn.is-active{ + background: #3aa7d8; + border-color: #3aa7d8; + color: #04111b; +} + +[data-theme="dark"] .audit-bar-created{ + background: linear-gradient(90deg, #3aa7d8, #60c0ea); +} + +[data-theme="dark"] .audit-bar-revenue{ + background: linear-gradient(90deg, #22c55e, #4ade80); +} + @media (max-width: 768px){ .audit-hero{ flex-direction: column;