Commits
CI's first day went well: it went red at night
We set up CI in the afternoon and that very night it failed: Google Fonts, typed bugs hidden for a day and a newsletter that silently cut at 999 subscribers. CI did its job.
Yesterday afternoon we closed the security block and set up a minimal CI workflow. The idea was simple: no build error should reach main in silence again. At night, CI did exactly its job. It failed. And it was right.
"CI failed - all jobs failed"
The two main runs at 23:39 and 23:44 were red. The message, at first, said little:
TypeError: Cannot read properties of null (reading '1')
And the awkward part: it was intermittent. The 21:07 runs had passed with the same layout. The culprit was next/font/google: it downloads woff2 files from Google Fonts at build time. If the runner reaches Google, it passes. If not, it dies with this cryptic TypeError.
It's a network dependency inside the build. Coherent, mind you, with what we already knew from §1b of the spec: the fonts had to be local.
Three chained findings
1. Local fonts. The woff2 files now live in src/fonts/ and load with next/font/local:
src/fonts/inter-latin-var.woff2
src/fonts/instrument-serif-latin-400.woff2
src/fonts/instrument-serif-latin-400-italic.woff2
Inter is variable: Google served the same file for all five weights, so a single file with weight: "100 900". Deterministic, network-free build.
2. TS2783 in the ingestion. Once the build stopped tripping on fonts, two type errors that had been there since the day before and nobody had seen appeared. In ingesta/publicar, the response did { ...yaExiste, id, ok: true }: the spread carried the destination document's id and overwrote the revision's. A real runtime bug —the response misidentified the revision— uncovered because yesterday's cache() sharpened the inferred type of findTarget.
3. The field was destinaris. In newsletter/enviar the field was written as destinataris, and r.error had no fallback. The stats update would have failed at runtime.
Verification: npm ci && npm run build with the same variables as CI, first reproducing the errors in this order and then in green.
The newsletter cut at 999
Reviewing newsletter/enviar we found this:
const lote = subs.docs.slice(0, 999);
// ponytail: only the first batch (SendGrid allows 1000/req).
// With >999 confirmed we'll need to paginate the call — trivial extension.
Worse: the newsletter was marked as sent with destinaris: 999. With 1,500 subscribers, 501 people received nothing and the panel said nothing. It now paginates in batches of 1,000 (SendGrid's real limit), destinaris stores the real total, if no batch goes out it returns 502 and marks nothing, and if some are missing it returns 207 with the detail.
Ingestion and the "already published" case
The audit said that retrying publication created duplicates. That was false: noticies_slug_idx is UNIQUE in the database and the retry crashed against the index. The real problem was another: if the create went well and the status update failed, the row stayed pending forever, and every retry failed with an opaque error. Now, before converting, the document is looked up by its natural slug; if it exists, it is closed as published and the response is yaPublicado: true with targetCollection and targetId. No migration needed: the natural key was already there.
What's left
Tasks 10, 11 and 22 to 24 of TASKS.md closed. P1 performance is nearly all done. P2 accessibility too. Tests remain (27) and the P3 design work, and the lists still run one query per visit: fixing that for real is a redesign, and we preferred to remove the dead revalidate = 3600 rather than fake a cache that doesn't exist.