Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
847689e
docs: contrato de la API tal como lo entrega el front
Sep 19, 2026
3c78d50
chore(backend): pytest y ruff como extra dev
Sep 19, 2026
04f034d
test(telemetry): suite del SDK, backfill y sembrado
Sep 19, 2026
95da2da
test(contrato): vocabulario de fallos cuadrado en los tres sitios
Sep 19, 2026
fcfc292
feat(telemetry): ciclo de vida del episodio para que Live sea en vivo
Sep 19, 2026
af17f3a
fix(sql): v_episode_summary expone started_at y ended_at
Sep 19, 2026
fae7928
fix(telemetry): ninguna respuesta malformada tumba un episodio
Sep 19, 2026
855b449
fix(seed): el sembrado vuelve a ser determinista entre procesos
Sep 19, 2026
1000601
test(contrato): relaciones y consultas verificadas contra Postgres
Sep 19, 2026
1a1e403
ci: ruff, pytest con Postgres y build del front en cada push
Sep 19, 2026
e5d95e4
docs: API.md al dia y referencias muertas al repo de simulacion
Sep 19, 2026
af7ec99
fix(live): la pantalla detecta un episodio nuevo sin recargar
Sep 19, 2026
46fde4c
fix(live): los KPIs en curso salen de los datos que ya estan llegando
Sep 19, 2026
f3dcc69
feat(seed): guiones de datos de ejemplo y de prueba en vivo
Sep 19, 2026
9a7056c
Merge branch 'main' into feature/backend-hardening
Sep 19, 2026
e5bc6e3
feat(sdk): config del run, snapshots y geometria compartida
Sep 19, 2026
d9a40f1
test(contrato): mensaje claro si falta psql
Sep 19, 2026
8a8c33d
feat(front): primera versión del front con Live y Ejecuciones (#1)
AnderVarela Sep 19, 2026
131b4e0
merge: front v1 de dev, con los arreglos de Live reaplicados
Sep 19, 2026
ac25216
Merge pull request #2 from STACKSPECT/feature/backend-hardening
manuamest Sep 19, 2026
849c0d9
feat(front): rediseño en blanco y negro con temas claro y oscuro (#3)
AnderVarela Sep 19, 2026
c2521be
chore: dev.sh levanta la plataforma con un solo comando
Sep 19, 2026
44c95b8
refactor(front): un solo .env, el de la raiz
Sep 19, 2026
6b693c4
feat(front): pantalla de detalle de una ejecución (#4)
AnderVarela Sep 19, 2026
e8fd075
fix(front): alzado a escala del pale y captura real junto al esquema
Sep 19, 2026
a37fe78
feat(front): Live solo enseña la ejecución en directo (#5)
AnderVarela Sep 19, 2026
5d9a0f0
Merge remote-tracking branch 'origin/dev' into feature/alzado-y-capturas
Sep 19, 2026
1070b19
Merge pull request #6 from STACKSPECT/feature/alzado-y-capturas
manuamest Sep 19, 2026
36d60fe
feat(vocabulario): nueva tarea paletizado-guionizado
Sep 19, 2026
2d04f2f
fix(live): el aviso de conexion perdida saltaba sin haberse perdido nada
Sep 19, 2026
f6389e3
docs: README, licencia MIT, guía de contribución y versiones clavadas…
manuamest Sep 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 5 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# Copia esto a .env (gitignorado) con los valores de tu proyecto de Supabase.
#
# Es el UNICO fichero de credenciales del repo: lo lee load_env() del lado Python
# y, a traves de frontend/next.config.ts, tambien el front. No hay .env.local.
# Project Settings -> Data API -> Project URL, y API Keys.

SUPABASE_URL=https://xxxxxxxxxxxx.supabase.co

# Pública: lectura bajo RLS. Es la que acaba en el navegador.
# Pública: lectura bajo RLS. Es la que acaba en el navegador, publicada por
# next.config.ts como NEXT_PUBLIC_SUPABASE_ANON_KEY.
SUPABASE_ANON_KEY=eyJ...

# SECRETA: se salta RLS. Solo el lado Python (SDK, backfill, seed). Nunca en el front.
Expand Down
80 changes: 80 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Lo mínimo que tiene que contestar un push: ¿aplica el SQL?, ¿siguen cuadrando el
# esquema y lo que el front consulta?, ¿compila la interfaz?
#
# El Postgres no es decoración: el último bug de esquema que se coló (`round()` sobre
# double precision) solo aparece EJECUTANDO el SQL, y el de `started_at` solo
# comparando las consultas del front contra las columnas de verdad.

name: CI

on:
push:
branches: ['**']
pull_request:

jobs:
backend:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: postgres
ports: ['5432:5432']
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-timeout 5s
--health-retries 10

env:
# El test crea su propio esquema desechable y solo acepta localhost.
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: '3.11' # el mínimo que declara pyproject

# El SDK no tiene dependencias; pytest y ruff viven en el extra dev.
- run: pip install -e './backend[dev]'

- run: ruff check backend

# psql viene de serie en ubuntu-latest; el test lo usa como cliente para no
# meter psycopg solo para esto.
- run: pytest backend -q

frontend:
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend

env:
# De pega, pero tienen que existir: createClient se evalúa al importar el módulo
# y sin ellas el build se cae. Son públicas por definición, así que aquí no hay
# nada que esconder; la service_role no aparece en el front jamás.
NEXT_PUBLIC_SUPABASE_URL: https://ejemplo.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY: clave-de-pega-para-compilar

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
cache-dependency-path: frontend/package-lock.json

- run: npm ci

- run: npm run lint

# `build` y no `tsc --noEmit`: Next 16 genera durante el build los tipos de
# PageProps<'/runs/[id]'>, que es justo lo que van a necesitar las rutas
# pendientes.
- run: npm run build
18 changes: 16 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Secretos. Las credenciales de Supabase viven en .env (service_role, lado Python) y
# en frontend/.env.local (solo la clave anon). Ninguno de los dos se versiona jamás.
# Secretos. Las credenciales de Supabase viven en UN solo fichero, el .env de la raíz:
# la clave anon y la service_role. No se versiona jamás. (Antes había además un
# frontend/.env.local con la anon; se quitó porque dos copias se desincronizan solas,
# pero el patrón .env.* lo sigue cubriendo por si alguien lo recrea.)
.env
.env.*
!.env.example
Expand Down Expand Up @@ -29,6 +31,9 @@ dist/
.mypy_cache/

# Editor / SO
# settings.local.json son los permisos de herramientas de cada cual, no del proyecto:
# .claude/settings.json (compartido) sí se versionaría; el local nunca.
.claude/settings.local.json
.vscode/
.idea/
*.swp
Expand All @@ -39,3 +44,12 @@ Thumbs.db
# Logs y volcados
*.log
core

# CodeGraph
.codegraph/

# Ejecuciones escritas por RunLog. El jsonl es la fuente de verdad en la maquina
# que lo produce, no algo que se versione: son datos, no codigo.
# Anclado a la raiz: sin la barra inicial ignoraba CUALQUIER carpeta llamada `runs`, y se
# tragaba en silencio las rutas del front (frontend/app/runs/[id]) y sus clientes.
/runs/
2 changes: 1 addition & 1 deletion .gitmessage
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# <tipo>(<ámbito>): <asunto en imperativo, sin punto, máx 50 caracteres>
#
# Tipos: feat fix docs style refactor test chore perf ci revert
# Ámbitos de este repo: sim perception planning control runner metrics configs scripts
# Ámbitos de este repo: front live sdk telemetry seed sql backend contrato vocabulario
#
# Cuerpo: explica el POR QUÉ, no el qué (el diff ya dice el qué).
#
Expand Down
97 changes: 80 additions & 17 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ percepción. Este repo solo sabe de episodios, métricas y pantallas.
backend/ SQL + SDK. Supabase ES el backend; no hay servidor propio.
sql/001_schema.sql tablas, vistas y RLS
sql/002_design.sql columnas y vistas que pide el diseño
sql/003_snapshots.sql las fotos del palé (solo la URL; los PNG van a Storage)
theker_telemetry/ el SDK que importa quien produce episodios
schema.py EpisodeResult, FAILURES, TASKS, RunWriter
core.py cliente PostgREST y RunLog
Expand All @@ -37,15 +38,30 @@ backend/ SQL + SDK. Supabase ES el backend; no hay servidor prop

frontend/ Next.js 16. Lee Supabase con la clave anon, bajo RLS.
app/ rutas: / (Live), /runs, /runs/[id], /runs/[id]/[seed]
components/ Pallet, Episode, Runs, primitives, TopBar
api/clients/ una función por endpoint de PostgREST
api/hooks/ los mismos, envueltos en TanStack Query
components/ atoms / organisms / screens
lib/ui.ts TODA conversión de unidades, en un solo sitio
lib/supabase.ts tipos, consultas y comparability()
styles/colors.css TODOS los colores (tema claro y oscuro), en un solo sitio
lib/pallet.ts geometría del dibujo; el tamaño del palé NO es fijo
lib/supabase.ts los tipos, que son la mitad del contrato

docs/
BRIEFING-observabilidad.md qué se construye y por qué
API.md el contrato: endpoints, tipos y vocabularios
design/ los 11 tableros del diseño, a 1440 px
img/ capturas y variantes de marca que usa el README

README.md cara pública del repo, en inglés: requisitos, instalación,
desarrollo, uso, dependencias con su auditoría de licencias.
CONTRIBUTING.md el flujo real: ramas, commits, comprobaciones y qué exige CI.
LICENSE MIT, 2026 STACKSPECT.
```

Los tres anteriores están **en inglés** a propósito, y son los únicos: son lo que lee
quien llega de fuera. Todo lo demás —este documento, `docs/`, los comentarios— sigue en
castellano.

**La dependencia va en un solo sentido.** La simulación importa `theker_telemetry`;
esta plataforma no sabe que MuJoCo existe. Por eso el esquema de métricas vive aquí y
no allí: quien define la forma del dato es quien lo almacena, y así reescribir la
Expand All @@ -59,6 +75,7 @@ simulación no se lleva por delante la observabilidad.
# 1. Esquema: pegar en el SQL editor de Supabase, en orden. Son idempotentes.
backend/sql/001_schema.sql
backend/sql/002_design.sql
backend/sql/003_snapshots.sql

# 2. Credenciales. .env en la raíz (gitignorado):
# SUPABASE_URL=https://xxxx.supabase.co
Expand All @@ -69,15 +86,33 @@ backend/sql/002_design.sql
pip install -e backend

# 4. Datos con los que ver la interfaz
python backend/seed/palletizing.py
python backend/seed/palletizing.py # histórico sembrado, 6 ejecuciones
python backend/seed/ejemplo_contrato.py # una entrada que toca TODO el contrato
python backend/seed/humo_live.py # monta un palé en vivo: comprueba Live
python backend/backfill.py --runs-dir <repo de la simulación>/simulation/runs

# 5. Front
# 5. Todo de una vez: comprueba credenciales, dependencias y esquema, y arranca.
./dev.sh # --check para solo comprobar, --seed para sembrar antes

# ...o solo el front, si ya sabes que lo demás está en su sitio
cd frontend && npm install && npm run dev

# 6. Tests. Sin DATABASE_URL corren los que no tocan la red; con él se añaden los
# que aplican el esquema de verdad y verifican el contrato con el front.
pip install -e 'backend[dev]'
docker run --rm -d --name pg -e POSTGRES_PASSWORD=postgres -p 5433:5432 postgres:16
export DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres
ruff check backend && pytest backend -q
```

`frontend/.env.local` lleva **solo** `NEXT_PUBLIC_SUPABASE_URL` y
`NEXT_PUBLIC_SUPABASE_ANON_KEY`. La `service_role` nunca entra ahí: se salta RLS.
**Un solo fichero de credenciales**, el `.env` de la raíz. Lo lee `load_env()` del lado
Python y, vía `frontend/next.config.ts`, también el front: ese fichero traduce
`SUPABASE_URL` y `SUPABASE_ANON_KEY` a sus `NEXT_PUBLIC_*` por **lista blanca**. No hay
`frontend/.env.local`, porque dos copias de lo mismo se desincronizan solas.

La `service_role` jamás sale de ahí: se salta RLS, y todo lo que lleva `NEXT_PUBLIC_` se
empaqueta en el JavaScript que descarga cualquiera. `next.config.ts` rompe el build si
una variable pública lleva un nombre que huela a secreto.

---

Expand Down Expand Up @@ -108,9 +143,15 @@ calculados de las vistas SQL. Dos sitios que suman lo mismo acaban discrepando.
**Medianas, no medias.** Un episodio que se derrumba a los 3 s arrastra la media del
tiempo de ciclo y hace parecer rápido a un run que va mal.

**Live nunca se queda en blanco.** Sin ejecución activa enseña la última terminada; si
se pierde la conexión congela lo último recibido y lo dice. Quedarse en blanco delante
del jurado es lo peor que puede hacer esa pantalla.
**Live solo enseña lo que está pasando ahora, y nunca se queda en blanco.** Muestra el
episodio `running`. Cuando acaba, se queda 10 s con su resultado («Terminado») para que se
vea el desenlace y para que, si arranca otro de la misma ejecución, el cambio sea directo
y no un parpadeo. Sin ninguno, lo dice con un mensaje («No hay ninguna ejecución en
directo») y **no** lo rellena con lo último terminado, porque eso se leería como si
estuviera ocurriendo. Si se pierde la conexión con un episodio en pantalla, congela lo
último recibido y lo dice. Lo terminado se mira en Ejecuciones. Quedarse en blanco delante
del jurado es lo peor que puede hacer esa pantalla; un mensaje claro no es quedarse en
blanco.

**El disco manda.** El `episodes.jsonl` que escribe la simulación es la fuente de
verdad; Supabase es una réplica consultable. Un fallo de red avisa una vez y el
Expand All @@ -122,10 +163,13 @@ episodio sigue: la demo no puede depender del wifi de la sala.

Cerrado. Añadir una nueva obliga a tocar **tres sitios**: `FAILURES` en
`backend/theker_telemetry/schema.py`, el CHECK de `episodes.failure` en
`backend/sql/002_design.sql`, y `FAILURE_TEXT` en `frontend/lib/ui.ts`. Saltarse el
`backend/sql/001_schema.sql`, y `FAILURE_TEXT` en `frontend/lib/ui.ts`. Saltarse el
segundo hace que el episodio se escriba en disco y la subida se caiga entera sin que
nadie se entere hasta ver la interfaz vacía.

Ya no hace falta acordarse: `backend/tests/test_vocabulario.py` compara los tres sitios
y falla si uno se queda atrás.

| enum | en pantalla |
|---|---|
| `no_detection` | no vio ningún paquete |
Expand Down Expand Up @@ -193,20 +237,39 @@ evolución en vez de borrón y cuenta nueva.

- Python 3.11, type hints en las firmas públicas. TypeScript estricto en el front.
- Unidades SI en el dato, milímetros y grados en la pantalla.
- Nombres de código en inglés; comentarios y documentos en castellano.
- **Colores solo como variables, y definidas en un único archivo:** `frontend/styles/colors.css`.
Ningún otro archivo (CSS, TS, TSX) lleva `#hex`, `rgb()`, `hsl()` ni un color con nombre;
consume `var(--…)`. Un color con transparencia es otra variable de ese archivo, calculada
con `color-mix` desde la base. `npm run lint` lo comprueba (`scripts/check-colors.mjs`).
- Nombres de código en inglés; comentarios y documentos en castellano. Las tres
excepciones son `README.md`, `CONTRIBUTING.md` y `LICENSE`, que van en inglés porque
son lo que lee quien llega de fuera (§2).
- Commits pequeños, con números cuando toquen comportamiento.
- El diseño de referencia es `docs/design/observality-platform-design.html`: un bundle
autoextraíble con 11 tableros. Se abre en el navegador.
autoextraíble con 11 tableros. Se abre en el navegador. Manda en la **estructura y el
contenido** de cada pantalla; **no** en el aspecto: la identidad visual es la del logo de
STACKSPECT (blanco y negro, esquinas suaves, Plus Jakarta Sans y Geist Mono), con tema
claro y oscuro. El color con significado (estados, oracle, sembrado, causas de fallo) se
conserva en los dos temas. `npm run lint:contrast` garantiza el contraste AA.

---

## 9. Estado

- **Backend: completo.** Esquema, SDK, backfill y sembrado, todos probados.
- **Front:** tokens, librerías, componentes y la pantalla **Live** con Realtime y sus
tres estados (sin ejecución, fallo, conexión perdida). Compila.
- **Pendiente:** las rutas `/runs`, `/runs/[id]` y `/runs/[id]/[seed]`. Son pegamento:
sus componentes ya están en `components/Runs.tsx` y `components/Episode.tsx`.
- **Backend: completo y con red.** Esquema, SDK, backfill y sembrado, con 116 tests y
CI. `pytest backend -q` no toca la red; exportando `DATABASE_URL` a un Postgres
desechable se añaden los que levantan el esquema de verdad y comprueban el contrato
con el front (`backend/tests/test_contrato.py`). El contrato está escrito en `docs/API.md`.
- **Episodio en vivo:** `RunLog` tiene `begin()` / `event()` / `pallet_state()` /
`end()` además de `episode()`. **La simulación tiene que llamarlas** o Live enseñará
el palé ya montado: `episode()` sube todo de golpe y entonces no hay fila `running`
ni `UPDATE` a los que Realtime pueda reaccionar.
- **Front:** `/` (Live) y `/runs` (Ejecuciones), sobre TanStack Query, con Realtime y
los tres estados no felices. El tamaño del palé sale de `config.pallet_size_m` vía
`palletSize()`: **no** se dibuja siempre a 1200x800, porque el palé real puede ser una
maqueta a escala y entonces todas las cotas saldrían mal por el mismo factor.
- **Detalle de ejecución:** `/runs/[id]` y `/runs/[id]/[seed]` ya están, sobre el mismo
`EpisodeDashboard` que Live: cambia de dónde salen los datos, no cómo se ven.

Aviso de Next 16: `params` es una Promise (`const { id } = await params`) y existen los
tipos globales `PageProps<'/runs/[id]'>`. Hay documentación offline en
Expand Down
Loading
Loading