content/auto-draft
needs attentionc27ea57 · incrementalpre-PRreviewed 2026-08-04 17:46 UTC3H · 5M · 4L · 6I- Purpose
- Content branch for publishing pillar blog posts on CFE tariff concepts
- Goal
- Publish 'demanda contratada de CFE' pillar post with 5 code-generated SVG visualizations
- Sub-goals
- New blog post demanda-contratada-cfe.mdx (225 lines, FAQs, Fuentes)
- 5 new SVG chart components in charts.tsx (ContractedDemandLadder, DemandUtilizationChart, PuntaDemandHeatmap, PeakTimeShareHistogram, LoadFactorComparisonChart)
- Register new charts in blog [slug] page mdxComponents
- Advance calendar: demanda-contratada-cfe idea→review; 3 prior posts review→published
- What
- Added 225-line MDX blog post with 7 FAQ entries and 5 embedded SVG charts. Added 460+ lines to charts.tsx with 5 new pure server-component SVG charts. Registered all 5 in the blog page mdxComponents. Updated calendar.yml to advance 4 entries.
- Why
- Part of the informational blog cluster targeting CFE tariff keywords; this is the demanda contratada pillar post following gdmth, gdmto-vs-gdmth, and factor-de-potencia.
- Areas
- apps/web/content/blog/demanda-contratada-cfe.mdx+225−0apps/web/src/components/blog/charts.tsx+460−0apps/web/src/app/(marketing)/blog/[slug]/page.tsx+2−2apps/web/content/calendar.yml+4−4
- Blast
- 4 files, +691/−6 lines, entirely in apps/web marketing content. No platform, domain, or infra code touched.
Findings · 16
correctness4
LoadFactorComparisonChart: profiles do not have the same energy — contradicts chart claim
apps/web/src/components/blog/charts.tsx:1571
peaky array sums to 3,030; flat array sums to 2,930 — a 100 kWh (3.4%) difference. The chart subtitle ('Mismo consumo mensual'), the aria-label ('la misma energía total'), and the figcaption all claim the two profiles deliver the same monthly energy. They don't. A reader who sums the values will notice the discrepancy and lose trust. Fix: add ~4 kWh to each of the 24 flat-profile hours to reach 3,030, or drop the 'mismo consumo' framing and say 'consumos similares'.
PeakTimeShareHistogram saving calc conflates all-hours peak with punta-block capacidad rate
apps/web/src/components/blog/charts.tsx:1492
The histogram shows demand distribution across ALL 720 monthly hours, then labels the month's peak as 'demanda facturable 235 kW' and computes savings at $386.37/kW (the GDMTH capacidad rate). For GDMTH, the capacidad charge is measured against the punta-block peak specifically; the all-month peak drives the distribución charge (a different, lower rate). If the 235 kW peak falls outside punta hours, the saving applies to distribución, not capacidad — a different formula and rate. The companion PuntaDemandHeatmap places the peak inside punta by construction (hourShape 0.88 for hours 18–21), but the histogram stands alone without this context. Add a note that the saving applies when the peak falls in the bloque de punta.
Mixed-tariff FC pool supports GDMTH-specific threshold claim without qualification
apps/web/content/blog/demanda-contratada-cfe.mdx:166
The post uses FC median 0.45 from 5,210 gran demanda bills (all tariffs mixed: GDMTO 0.55, GDMTH 0.57, DIST 0.74, DIT 0.71) to claim 'cerca de tres de cada cuatro recibos' have FC < the GDMTH threshold of 0.57. This fraction is GDMTH-specific; applying it universally to a mixed pool is imprecise. For DIST bills (FC threshold 0.74), even more bills fall below the threshold; for DIT, the fraction changes too. The claim is directionally reasonable for the GDMTH majority but should note the mixed-tariff caveat.
Deposit approximation '$48,000' rounds down when the actual figure is ~$48,683
apps/web/content/blog/demanda-contratada-cfe.mdx:139
The post states 'unos $48,000 pesos inmovilizados' for 63 kW idle × $386.37/kW × 2 = $48,682.62. Rounded to the nearest thousand this is $49,000, not $48,000. The 'unos' qualifier softens it but the rounding goes in the wrong direction. Minor but easily fixed.
security1
Security: no findings
MDX compiled at build time (static). Chart components render hardcoded illustrative SVG — no user input, no dangerouslySetInnerHTML, no external data fetched. No security concerns.
conventions3
CTAs use plain markdown links, not Button component
apps/web/content/blog/demanda-contratada-cfe.mdx:211
Two in-body CTAs (→ [Calcula…](/calculator) and → [Solicita una demo](/demo)) use plain markdown arrow-link syntax. blog-authoring.md §7 is explicit: 'CTAs use the Button from @batu/ui: variant="cta" + variant="secondary". Not ad-hoc <a> pills.' These render as unstyled body links — a brand violation and missed conversion. Fix: use `<Button variant="cta" asChild><a href="/calculator">…</a></Button>` via MDX JSX.
TODO screenshot placeholder remains while draft: false and calendar status is 'review'
apps/web/content/blog/demanda-contratada-cfe.mdx:205
Line 205 has a JSX comment for a pending product screenshot (bills table showing Demanda Contratada / Medida / Facturable columns). blog-authoring.md non-negotiables: 'no TODO visual placeholders left (except a pending real screenshot)'. The post is marked draft: false and its calendar entry was flipped to 'review' in this same commit. The exception clause allows a pending screenshot, but the prose sets up the expectation of product proof that is not delivered — the section reads as incomplete. Either add the anonymized PostImage before setting draft: false, or keep it as draft until the screenshot exists.
Chart accessibility, title/description lengths, gender agreement, and CFE grounding all pass
apps/web/src/components/blog/charts.tsx
All 5 new SVG components have role='img' + descriptive aria-labels. metaTitle 51 chars (≤60 ✓), description 153 chars (150–160 ✓). CFE period gender agreement is correct. Fuentes section cites CT/11.SE/8-2025 and A/064/2018. FC values (0.55/0.57/0.74/0.71) match cfe-tariffs convention.
tests1
PeakTimeShareHistogram arithmetic untested; CAP_RATE inconsistent with charts.tariffs.ts
apps/web/src/components/blog/charts.tsx:1470
tailPct, tailHours, demandDrop, and saving are reader-visible figures computed inline inside JSX, not extracted to the tested charts.tariffs.ts module. Additionally, CAP_RATE is hardcoded as 386.37 (labeled jul-2026) while RATES.gdmth.cap in charts.tariffs.ts is 394.26 (jun-2026). These are intentionally different months but there is no lock test, so if charts.tariffs.ts is updated for a new month the histogram silently diverges. Risk: factually wrong peso saving shown to readers. Fix: extract CAP_RATE from RATES or document the deliberate period difference with a comment, and add a unit assertion for saving.
improvement4
ContractedDemandLadder hero chart precedes definitions of all four demand concepts
apps/web/content/blog/demanda-contratada-cfe.mdx:32
The hero <ContractedDemandLadder /> appears at line 32, immediately after the intro paragraph, before 'demanda derivada de la energía' or 'demanda facturable' are defined anywhere in the post. Readers encounter a chart showing four labeled bars (contratada 250 kW, medida 180 kW, derivada 158 kW, facturable 158 kW) for concepts not yet introduced. Blog-authoring convention: introduce visuals in text before they appear. The chart works well as a preview anchor, but the surrounding copy should name the four concepts and promise the article will explain each before placing the chart.
PeakTimeShareHistogram punchline saving figure reads as universal, not illustrative
apps/web/src/components/blog/charts.tsx:1543
The bottom punchline box reads '…y ese tramo fija tu demanda facturable. Recortarlo: −15% de demanda ≈ $13,523 MXN/mes en capacidad.' — no inline illustrativo qualifier. The figcaption does qualify it, but the visually prominent punchline box does not. Brand voice rule: 'Never state an illustrative figure as universal.' Fix: append '(ejemplo ilustrativo)' or prefix 'En este ejemplo,' to the punchline text.
PuntaDemandHeatmap legend shows only color swatches — no kW thresholds
apps/web/src/components/blog/charts.tsx:1450
The legend renders 5 colored squares labeled only 'bajo → pico'. The color buckets represent meaningful thresholds (<55%, 55–72%, 72–85%, 85–96%, ≥96% of the 235 kW peak), but sighted readers cannot decode the actual kW ranges from the swatches. The key pedagogical claim ('the peak is decided by a handful of cells') would be stronger if the top bucket were annotated with its threshold (e.g., '≥225 kW'). The aria-label covers screen readers.
LoadFactorComparisonChart: FC gap bar at left edge has no label, may confuse readers
apps/web/src/components/blog/charts.tsx:1611
A vertical colored bar at x=ox+14 spans from Davg to Dmax in each panel — intended to show 'the gap that IS the load factor'. Without a label this bar reads as an unexplained annotation to readers unfamiliar with load factor diagrams. Adding 'brecha FC' or a connector to the FC badge below would close the interpretive gap.
seo3
Generic OG image — no post-specific social card for this pillar post
apps/web/src/app/(marketing)/blog/[slug]/page.tsx:117
generateMetadata hardcodes /og-image.png for all blog posts. For a primary keyword pillar post ('demanda contratada CFE'), a post-specific OG card with the article headline would improve CTR from social/messaging shares. Not a blocking issue — the fallback is valid — but worth noting as a future improvement.
FAQPage JSON-LD backed by visible H3+paragraph content — compliant
apps/web/src/app/(marketing)/blog/[slug]/page.tsx:269
FAQ items are rendered as visible H3+paragraph pairs AND emit FAQPage JSON-LD from the same frontmatter array. Google's 'schema must match visible content' requirement is satisfied.
Sitemap, Article JSON-LD, internal links, and SVG crawlability all sound
apps/web/src/app/(marketing)/blog/[slug]/page.tsx
getAllPostsMeta() picks up the new post automatically (draft: false). Article + BreadcrumbList + FAQPage schemas auto-generated. All 4 cross-linked posts (/blog/como-leer-recibo-cfe, /blog/tarifa-gdmth, /blog/gdmto-vs-gdmth, /blog/factor-de-potencia-cfe) exist. SVGs render server-side as inline HTML — fully crawlable.
History · 14 commits
- c27ea57needs attentionincremental3H · 5M · 4L2026-08-04 17:46current
- ff69645needs attentionincremental0H · 6M · 8L2026-07-28 18:21
- 0257d74safeincremental0H · 0M · 0L2026-07-20 23:00
- 0795a9dsafeincremental0H · 0M · 2L2026-07-20 22:42
- 5c57414needs attentionincremental2H · 7M · 10L2026-07-20 22:29
- 5a57a8fneeds attentionincremental0H · 5M · 8L2026-07-20 17:14
- cc9056eneeds attentionfull1H · 3M · 9L2026-07-20 16:40
- 8b79d33needs attentionincremental5H · 7M · 11L2026-07-14 18:10
- f0c337cneeds attentionincremental1H · 1M · 5L2026-07-14 17:15
- 5c9de97needs attentionincremental0H · 5M · 7L2026-07-14 17:02
- 92cce08needs attentionincremental1H · 4M · 3L2026-07-14 02:00
- b7dd610needs attentionincremental0H · 6M · 7L2026-07-14 01:43
- 8c43554needs attentionfull0H · 2M · 1L2026-07-14 01:15
- 2149391blockedfull8H · 6M · 7L2026-07-10 17:30