← all branches

content/auto-draft

needs attentionviewing older commit
5c57414 · incrementalPR #325reviewed 2026-07-20 22:29 UTC2H · 7M · 10L · 8I
The branch
Purpose
Autonomous content draft for the GDMTO vs GDMTH comparison blog post — a primary keyword cluster target (informational: tarifa GDMTH, GDMTO vs GDMTH).
Goal
Publish a complete, factually-grounded GDMTO vs GDMTH guide with interactive SVG charts backed by real CFE tariff rates, verified by unit tests.
Sub-goals
  • SG-1: MDX content — full article with hero chart, load profiles chart, demand chart, FAQ structured data
  • SG-2: SVG charts in charts.tsx — GdmtoVsGdmthDecisionChart, LoadProfilesChart, GdmtoVsGdmthCostChart, DemandaFacturableSolarChart
  • SG-3: Real CFE tariff rates (VdM Centro, jun-2026) grounding the cost comparison
  • SG-4: Pure math module (charts.tariffs.ts) extracted for testability + unit tests (charts.calculations.test.ts)
  • SG-5: llms.txt updated with API + blog guide links for GEO
The changes (whole branch)
What
This incremental pass clears remaining loop-review nits: (1) MDX prose polished — em-dashes replaced with parentheses, multi-bullet paragraphs condensed; (2) llms.txt gets /developers and Guías destacadas section; (3) charts.tariffs.ts extracted as a new pure module with RATES/PROFILES/bill functions; (4) charts.calculations.test.ts adds unit tests locking the published peso figures; (5) charts.tsx visual fixes — decision chart height (caption clipping), cost segment colors, immutable stacked bars, winner pill, LoadProfilePanel component extraction, band overflow fix.
Why
Lock the math (tests prevent silent arithmetic drift that would mislead readers), fix visual regressions from previous passes (clipped captions, grey energía segment, band overflow), and improve GEO signal in llms.txt.
Areas
apps/web/content/blog+1780apps/web/src/components/blog+4402apps/web/public/llms.txt+60apps/web/src/app/(marketing)/blog+22
Blast
7 files, +627/−6 lines. All changes confined to apps/web — blog content, blog chart components, and llms.txt. No domain logic, no API, no infrastructure.
content-only apps/web no-backend-changes
ci· No CI checks found for PR 325coderabbit· No .coderabbit.yaml in repo

Findings · 27

correctness2

low

dfCap test provides no regression protection against formula change

apps/web/src/components/blog/charts.calculations.test.ts:24

Same root cause as the tests lens finding — tautological assertion. Low correctness impact since RATES.days is a const, but flagged for completeness.

low

GDMTH capacidad-wiring test is indirect

apps/web/src/components/blog/charts.calculations.test.ts:52

`gh.capacidad < go.capacidad` doesn't structurally assert `gh.capacidad === dmaxPunta(concentrado) * RATES.gdmth.cap`. A regression that uses dmaxMonth for GDMTH capacidad could still pass if the resulting charge remained lower.

security1

info

No security concerns

All SVG is server-rendered static. No dynamic user data, no innerHTML, no network calls. Pass.

conventions9

high

File-level docstring explains WHAT, not WHY

apps/web/src/components/blog/charts.tariffs.ts:1

The opening JSDoc block describes what the module contains rather than why it was split. The only non-obvious WHY worth keeping is the testability rationale ('importing the JSX module stalls vitest on transform'). Trim to that one line.

high

Second docstring is a content summary of RATES

apps/web/src/components/blog/charts.tariffs.ts:8

The JSDoc above RATES describes what the constant contains — readable from the const itself. The only worth-keeping fact is source + effective date. Reduce to a single inline comment.

medium

PROFILES docstring restates data shape

apps/web/src/components/blog/charts.tariffs.ts:18

Most of the PROFILES JSDoc ('two 24 h load curves, same kWh, different shape') is readable from the const. The ToU block boundaries `base [0,6), punta [18,22)` are non-obvious and worth one line — the rest can be removed.

medium

Test file opening docstring describes what tests do

apps/web/src/components/blog/charts.calculations.test.ts:1

The block comment explains what the functions produce and that tests 'lock the maths' — both visible from the test names. Keep only the last sentence ('Expected values recomputed from RATES by hand') which justifies the hardcoded expected values.

low

REF_KW inline comment is a 'what', not a 'why'

apps/web/src/components/blog/charts.tariffs.ts:16

'GDMTH eligibility threshold, drawn as a reference line' restates what the variable name already conveys. Remove.

low

blockKWh and dfCap JSDoc comments describe the obvious

apps/web/src/components/blog/charts.tariffs.ts:22

'Monthly kWh split by ToU block, from the curve.' and 'Demanda-facturable cap: Energía ÷ (FC · 24 · días).' restate what the function names and signatures already communicate. Remove.

info

Winner pill comment is the one justified WHY in this diff

apps/web/src/components/blog/charts.tsx

The comment explaining full opacity ('dimming the loser turned its dark-violet energía segment grey — indistinguishable from cargo fijo') documents a non-obvious visual failure mode. Keep it.

info

Height change comment is acceptable

apps/web/src/components/blog/charts.tsx:682

'room for the threshold captions at y≈298 (was 300 → descenders clipped)' explains a magic number and prior failure mode. Justified.

info

LP_* constant extraction is appropriate

apps/web/src/components/blog/charts.tsx:857

Pure configuration with no closure dependency — hoisting reduces re-allocation. Noted as info since improvement lens flags the namespace aspect separately.

tests7

medium

dfCap test is tautological — verifies the formula against itself

apps/web/src/components/blog/charts.calculations.test.ts:34

The test computes `90000 / (fc * 24 * 30)` inline — the same expression the implementation uses. It passes even if the formula is wrong. Replace with a concrete expected value computed offline, e.g. `expect(dfCap(90000, 0.55)).toBeCloseTo(227.27, 1)`.

medium

dfCap cap-binding branch is never exercised

apps/web/src/components/blog/charts.calculations.test.ts:39

Both profiles have peak demand well below dfCap, so `Math.min(dmaxMonth, dfCap(...))` always returns dmaxMonth. The branch where dfCap is the binding constraint (high-load-factor site) is a real code path in both bill functions but is never tested. A curve like `Array.from({length:24}, () => 250)` (180 000 kWh/mo, dfCap ≈ 454 kW < 250 false — actually 454 > 250 so still dmaxMonth — try 350 kW flat) would trigger it.

medium

GDMTH distribución demand basis has no direct assertion

apps/web/src/components/blog/charts.calculations.test.ts:56

The test confirms `gh.capacidad < go.capacidad` (punta vs month peak) but never asserts that GDMTH's distribución uses dmaxMonth. If both charges were accidentally wired to dmaxPunta the bill totals change (covered by the snapshot), but the structural rule is not an explicit invariant.

low

dmaxMonth is exported but not directly tested

apps/web/src/components/blog/charts.calculations.test.ts:2

dmaxMonth is exercised only indirectly through bill functions. A direct assertion like `expect(dmaxMonth(concentrado)).toBe(206)` would make the intent explicit.

low

Bill totals tested with Math.round — sub-peso drift undetected

apps/web/src/components/blog/charts.calculations.test.ts:41

Rounding to integer allows ±0.49 peso drift per assertion. `toBeCloseTo(expected, 1)` on the raw float would tighten the guard without changing test intent.

info

2.0–2.7 $/kWh range bounds not sourced

apps/web/src/components/blog/charts.calculations.test.ts:65

The range sanity guard is reasonable but bounds aren't derived from any CFE reference. If rates update, the test would silently pass even with implausible values. Consider annotating the source.

info

Tests cover only the two canonical PROFILES

apps/web/src/components/blog/charts.calculations.test.ts:10

No edge-case curves tested (all-zeros, single spike, dfCap-triggering high-LF curve). Acceptable scope for an article-claims test suite, but the dfCap branch gap (medium finding) could be closed with a synthetic fixture.

improvement4

medium

Import from charts.tariffs placed at bottom of module

apps/web/src/components/blog/charts.tsx:733

`import { RATES, REF_KW, PROFILES, billGdmth, billGdmto, type Profile } from "./charts.tariffs"` appears after hundreds of lines of existing code. ES imports are hoisted so it works, but bottom-of-file placement violates universal convention and confuses readers scanning the file header for dependencies. Move to the top.

low

reduce stacking adds complexity without correctness benefit

apps/web/src/components/blog/charts.tsx:813

The `SEG.reduce(...)` builds an intermediate array then immediately maps it — two passes, extra allocation, non-null assertion, nested spread. The original `let cum = 0` was scoped safely per bar render. In a server-rendered SVG this is trivial overhead, but the added complexity is not justified by the stated immutability goal.

low

LP_* module-level constants serve only LoadProfilesChart

apps/web/src/components/blog/charts.tsx:857

Six LP_* constants are module-scoped but used by a single component. The LP_ prefix is a naming workaround for the lack of lexical scoping. They could live inside or directly above the component.

info

LoadProfilePanel key omission on root g is intentional and correct

apps/web/src/components/blog/charts.tsx:881

Old closure returned <g key={p.name}>; named component returns <g> without key. Since LoadProfilePanel is called directly (not in .map), no key is needed — this is the correct fix.

seo4

low

Verify /developers route is live before shipping

apps/web/public/llms.txt:4

The new llms.txt entry links https://batuenergy.com/developers. Confirm the route is live and server-rendered; a 404 in llms.txt signals errors to AI crawlers.

low

Prose condensation removes minor keyword signal

apps/web/content/blog/gdmto-vs-gdmth.mdx

Removing the em-dash parenthetical '—lo que casi todos vienen a buscar—' slightly weakens explicit search-intent matching in the intro. The bold on 'cuál te conviene' partially compensates. Minimal SEO impact.

info

Guías destacadas section improves GEO extractability

apps/web/public/llms.txt:12

Adding labeled blog URLs for primary-keyword topics (GDMTO vs GDMTH, cómo leer recibo CFE, tarifa GDMTH) directly targets the informational cluster in CLAUDE.md. Good GEO signal.

info

Blog URLs in llms.txt use canonical apex host

apps/web/public/llms.txt:13

All new URLs use https://batuenergy.com/blog/* — no www, no trailing slash inconsistency. Consistent with canonical host convention.

History · 14 commits

  1. c27ea57needs attentionincremental3H · 5M · 4L2026-08-04 17:46
  2. ff69645needs attentionincremental0H · 6M · 8L2026-07-28 18:21
  3. 0257d74safeincremental0H · 0M · 0L2026-07-20 23:00
  4. 0795a9dsafeincremental0H · 0M · 2L2026-07-20 22:42
  5. 5c57414needs attentionincremental2H · 7M · 10L2026-07-20 22:29current
  6. 5a57a8fneeds attentionincremental0H · 5M · 8L2026-07-20 17:14
  7. cc9056eneeds attentionfull1H · 3M · 9L2026-07-20 16:40
  8. 8b79d33needs attentionincremental5H · 7M · 11L2026-07-14 18:10
  9. f0c337cneeds attentionincremental1H · 1M · 5L2026-07-14 17:15
  10. 5c9de97needs attentionincremental0H · 5M · 7L2026-07-14 17:02
  11. 92cce08needs attentionincremental1H · 4M · 3L2026-07-14 02:00
  12. b7dd610needs attentionincremental0H · 6M · 7L2026-07-14 01:43
  13. 8c43554needs attentionfull0H · 2M · 1L2026-07-14 01:15
  14. 2149391blockedfull8H · 6M · 7L2026-07-10 17:30