Repository navigation
feat: [TESIS-148] aggregate the real sales and dispatches for the reports screen - #101
Conversation
…reports screen The reports screen (S14) shows a fixed sample dataset because there was no aggregates endpoint. Part of it can be computed with today's model, and real numbers of the company are worth more in the demo than an invented dataset. GET /api/v1/reports/overview?period=7d|30d|90d answers, for the last N calendar days in Argentina time and against the previous window of the same length: orders and revenue without cancelled ones, units dispatched (by the dispatch event of the shipment log, since shipments keep no dispatch date), a daily or weekly curve with its empty buckets, and the shipments of each carrier with how many were delivered. On-time rate and anomalies come as an explicit null: the model has nothing to compute them from. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ADR-015 asks every new endpoint to join the contract spec: the overview is a bare resource with fixed keys, and so are its KPIs, curve points and carriers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Revisado. Lo veo bien para implementar. Es el que más me interesa de los que agregan funcionalidad, y no por lo que suma sino por lo que saca: hoy la pantalla de Reportes (S14) muestra un dataset inventado con el cartel «Datos de muestra». RF-26 (Panel de Control Analítico) es alcance comprometido y está en la línea base. Defender un panel con números fabricados es el riesgo más caro de toda esta tanda de PRs. Lo que más me gustó
Lo que verifiqué
Un nit de volumen
Antes de mergear
|
TomasMartin2004
left a comment
There was a problem hiding this comment.
Revisión del diff completo. El código es idéntico al que leí cuando los PRs estaban en draft —ningún commit nuevo—, así que lo que sigue es el veredicto formal.
✅ Aprobado
Es el que más me interesa de los que agregan funcionalidad, y no por lo que suma sino por lo que saca: hoy S14 muestra un dataset inventado con el cartel «Datos de muestra», y RF-26 es alcance comprometido. Defender un panel con números fabricados es el riesgo más caro de toda la tanda.
Lo que más me gustó
on_time_delivery_rate y active_anomalies van en nil explícito en vez de un número. Es la decisión correcta y la difícil: no hay fecha comprometida en shipments ni entidad de anomalía, y devolver 0 ahí diría «no hay anomalías» cuando lo cierto es «el modelo no lo registra». Da menos bloques que el diseño y todos los que da son verdad.
Lo que verifiqué
- Todo pasa por modelos con
CompanyScoped: no hay unwhere(company_id:)suelto que se pueda olvidar. Reports::Windowcorta el día en hora de Argentina y no en UTC, con la justificación sacada de la exclusión 9 de E4b. Es el error clásico y está contemplado.trendeddevuelvenilcuando el período anterior está en cero, en vez de un «+100 %». Con la base recién sembrada ese caso es el habitual.dispatched_shipmentssale del primer eventoready_to_shipporqueshipmentsno guarda cuándo se despachó. Correcto contra el modelo de hoy; si algún día se agregadispatched_at, el cálculo cambia de lugar.carriersresuelve con dosgroup().county unincludes(:service): sin N+1.
🟡 Un nit de volumen
curve hace pluck(:created_at, :total_amount) y agrupa en Ruby: con period=90d y una empresa con movimiento, trae todas las órdenes de la ventana a memoria en cada request. Para el MVP no molesta, pero es lo primero que se va a notar con volumen. Si lo querés cerrar ahora, un group_by_day en SQL lo resuelve.
⚠️ Antes de mergear
CONFLICTING por el merge de Shopify, y va antes de proyecto-web#64.
…s-overview Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ticket de Jira
https://proyectofinalfrlp.atlassian.net/browse/TESIS-148
Descripción
La pantalla de Reportes (S14) muestra un dataset de muestra fijo, con el distintivo «Datos de muestra», porque no existe un endpoint de agregados (TESIS-64). Los comentarios de esa card tienen razón en que varios bloques no tienen dominio detrás, pero una parte sí se puede calcular con el modelo de hoy. Para la demo, números reales de la empresa —aunque sean menos bloques— valen más que un dataset inventado. Este PR agrega
GET /api/v1/reports/overview?period=7d|30d|90d.Qué responde:
{ "period": "7d", "from": "2026-09-26T00:00:00-03:00", "to": "2026-10-02T01:30:00-03:00", "granularity": "day", "kpis": { "orders": { "value": 12, "trend": 20.0 }, "revenue": { "value": 84500.0, "trend": null }, "dispatched_units": { "value": 31, "trend": -8.5 }, "on_time_delivery_rate": null, "active_anomalies": null }, "curve": [{ "date": "2026-09-26", "orders": 2, "revenue": 12000.0 }, "…"], "carriers": [{ "company_integration_id": 3, "name": "Andreani", "dispatched": 5, "delivered": 2 }] }Decisiones que conviene mirar:
La ventana va en hora de Argentina. Los últimos N días calendario, desde las 00:00 del primero hasta ahora, en
America/Argentina/Buenos_Airesy no en la zona del servidor (UTC): con el corte en UTC, una venta de las 22 h caería en el día siguiente. El MVP opera sólo en Argentina (E4b, exclusión 9). El período anterior es la ventana del mismo largo inmediatamente antes, y contra él se calculatrend.trend: nullsi el anterior es cero. Un «+100 %» o un «0 %» contra nada se leería como un dato. El front ya modela así la falta de comparación (TrendedValue.trend: number | null).Unidades despachadas, por la fecha del despacho.
shipmentsno guarda cuándo se despachó: lo dice el eventoready_to_shipque escribeConfirmDispatchen la bitácora. Se suman las unidades de las órdenes de esos envíos, sin importar cuándo se creó la orden.Lo que no se puede calcular va como
nullexplícito. Cumplimiento de plazo (no hay fecha comprometida enshipments) y anomalías (no hay entidad). En vez del «% en plazo» por operador del diseño,carriersdice cuántos envíos despachó cada integración de courier y cuántos ya se entregaron: es lo que el modelo puede afirmar.La curva incluye los buckets vacíos, un punto por día (7d/30d) o por semana empezando el lunes (90d). Sin los días en cero, la línea uniría dos ventas como si hubiera habido ventas en el medio.
Sin facturación de canceladas. Las órdenes
cancelledno cuentan ni enordersni enrevenue.Reports::Window(período, rangos, buckets) yReports::BuildOverview(agregados) en el dominio nuevoapp/poros/reports/.ReportsController#overviewconReportPolicy#overview?(política sin registro: el aislamiento lo daCompanyScopeden cada modelo consultado).GET /api/v1/reports/overview. Un período desconocido o en forma de lista responde 400.reportsendocs/guidelines/feature-structure.md(sólo lee deordersyshipments).Evidencia visual
N/A
Cómo probar
Precondición:
bin/rails db:seed, login con un usuario de Norte.GET /api/v1/reports/overview→period: "7d", 7 puntos encurve, los KPIs de la semana.GET /api/v1/reports/overview?period=90d→granularity: "week", puntos en lunes.ordersyrevenuesuben; la orden aparece en el punto de hoy.dispatched_unitssuma sus unidades y el courier aparece encarriers.?period=1y→ 400{"error": "period must be one of 7d, 30d, 90d"}.Verificación:
bundle exec rspec(1591 ejemplos, 0 fallas, cobertura 99.89% línea / 99.81% rama),rubocopybrakemanlimpios.Impacto y consideraciones
¿Introduce breaking changes?
No. Endpoint nuevo.
¿Requiere nuevas variables de entorno?
No
¿Afecta la arquitectura o genera un nuevo patrón?
Suma un dominio de sólo lectura,
reports, documentado enfeature-structure.md. No requiere ADR: no cambia ninguna convención (la respuesta es un recurso pelado, como fija ADR-015).Escala: la curva agrupa en Ruby sobre
pluck(:created_at, :total_amount)de la ventana, para cortar el día en hora local sin depender delAT TIME ZONEde Postgres. Con el volumen del MVP es instantáneo; si una empresa llegara a decenas de miles de órdenes por trimestre, conviene pasarlo adate_truncen SQL.Front: la card 008 (
proyecto-web) reemplaza el dataset de muestra por este endpoint.🤖 Generated with Claude Code