# Pedrito Hechakuaa — Instrucciones para Claude Code ## Qué es esto Pedrito Hechakuaa: un bot de Telegram que monitorea notificaciones pendientes en el sistema judicial de Paraguay (`apps.csj.gov.py`) y avisa cuando aparece algo nuevo. Uso propio de un solo abogado (single-tenant). **Versión actual: v0.3.1** — ver historial completo en `CHANGELOG.md`. **Scope implementado:** - Login al PJ + polling de notificaciones pendientes (multi-tenant) - Diff vs snapshot por tenant → solo notifica lo nuevo - Telegram como canal de salida con onboarding, tono personalizable y timezone - Búsqueda de expedientes y descarga de PDFs - Infraestructura: Docker, health check HTTP, schema migrations, webhook opt-in **Fuera del scope actual (no agregar sin consulta explícita):** - Motor de reglas de plazos - LLM / conversación libre - Web de onboarding - Actuaciones de expedientes (es v1) - Acuse de notificaciones (nunca — efecto procesal irreversible) --- ## ⚠️ Invariantes de seguridad — NUNCA se violan, aunque sea v0 ``` ✅ Credenciales cifradas con Fernet en .env — desencriptar en memoria, usar, descartar ✅ .env en .gitignore desde el primer commit ✅ Todo log pasa por scrub() en utils/sanitize.py antes de escribirse ✅ Audit trail mínimo: quién hizo qué, cuándo, resultado — SIN payload sensible ✅ El bot solo responde al TELEGRAM_CHAT_ID configurado (no es público) ❌ PUT /Notificaciones/{id}/recibir — NUNCA. Acusa notificaciones, efecto procesal irreversible ❌ Password, bearer token, o cualquier credencial en logs (ni en DEBUG) ❌ print() para debug — SIEMPRE usar logger ❌ .env con valores reales en el repo ``` **`scrub()`** (`from utils.sanitize import scrub`): función en `utils/sanitize.py` que recibe cualquier string y reemplaza patrones sensibles (tokens, credenciales) antes de loggear. Toda llamada a `logger.*()` con datos externos pasa por acá. --- ## Arquitectura ``` main.py ← entrypoint, arranca bot + poller en paralelo config.py ← Settings via pydantic-settings (ÚNICA fuente de config) csj_client.py ← HTTP client del PJ (login + notificaciones, read-only) poller.py ← loop de polling + diff + dispara alertas telegram_bot.py ← envía mensajes y maneja /comandos básicos storage.py ← snapshot en JSON local (sin DB por ahora) utils/ sanitize.py ← credential_scrubber() — NUNCA loggear sin pasar por acá ``` --- ## Stack (sin cambios hasta nuevo aviso) | Capa | Tecnología | |------|-----------| | Lenguaje | Python 3.12 con type hints estrictos | | HTTP client | `httpx` async | | Bot | `python-telegram-bot v21` async | | Scheduler | `APScheduler 3` | | Cifrado | `cryptography` (Fernet) | | Config | `pydantic-settings` | | Logging | `structlog` (JSON en prod, ConsoleRenderer en dev) | Sin Playwright. Sin Postgres. Sin Redis. Sin LLM. Sin Docker por ahora (opcional). **No introducir librerías fuera de esta lista sin justificación explícita.** --- ## Sistema judicial target **URL base**: `https://apps.csj.gov.py` **Docs**: `docs/reconocimiento/SISTEMA_NUEVO_RECON.md` y `MANUAL_SANITIZADO.md` ### Flujo de autenticación (confirmado por reconocimiento) ``` 1. GET https://apps.csj.gov.py/login → Cookie: cookiesession1={hex32} 2. POST https://apps.csj.gov.py/autenticador/login Headers: Cookie: cookiesession1={hex32} Body JSON: {"usuario": "{cedula}", "clave": "{password}", "temporal": false} → bearerToken (TTL 1h), refreshToken, timestampExpiracionUtc 3. Todos los requests siguientes llevan: Cookie: cookiesession1={hex32} Authorization: Bearer {bearerToken} usuario-rol: 16 ← 16 = Abogados ``` ### Endpoints de notificaciones (confirmados) ``` GET /api/gestion-partes/Notificaciones/CantidadNotificacionesPorRecibir → int (cantidad de notificaciones pendientes) GET /api/gestion-partes/Notificaciones/PorRecibir?offset=0&cantidad=50 → lista de NotificacionPendiente ``` ### Campos relevantes de NotificacionPendiente ```json { "codNotificacionOrigen": 1234567, "nroExpedienteNumero": 1234, "nroExpedienteAnio": 2024, "caratula": "Rodríguez c/ Municipalidad Capital", "descripcionActuacion": "Auto Interlocutorio", "descripcionTipoActuacion": "Providencia", "fecha": "2026-05-20", "juzgado": "1er Juzgado Civil Capital", "permisoFirmado": "JWT...", "codCasoJudicial": 103708, "origen": 0 } ``` --- ## Estados de conversación El bot es simple pero tiene estados implícitos que deben manejarse explícitamente: ``` UNREGISTERED → usuario no autorizado (responder a cualquier message) REGISTERED_IDLE → autorizado, sin operación en curso (estado normal) CHECKING → revisión de notificaciones en curso (no disparar otra) ERROR_AUTH → fallo de auth del PJ (bot pausado hasta intervención) ERROR_PASSWORD_TEMP → password temporal detectada (bot pausado) ``` --- ## Manejo de errores — mensajes accionables **Nunca**: "Error inesperado" o mensajes técnicos al usuario. **Siempre**: qué pasó + qué puede hacer el usuario. | Situación | Mensaje al abogado | |-----------|-------------------| | Login fallido por credenciales | "No pude conectarme al PJ. Las credenciales podrían haber cambiado. Contactá al administrador." | | Password temporal | "Tu contraseña del PJ es temporal. Cambiala en apps.csj.gov.py y luego reiniciá Pedrito Hechakuaa." | | Error de red transitorio | "No pude conectarme al sistema judicial. Voy a reintentar en la próxima ronda ({hora})." | | 5 errores consecutivos | "El sistema del PJ estuvo inaccesible por un tiempo. Si el problema persiste, revisá apps.csj.gov.py directamente." | Emojis con criterio: ✅ éxito, ❌ error, ⚠️ advertencia, ⏳ procesando. No decorativos. --- ## Estructura del snapshot (storage.py) JSON local en `data/snapshot.json`: ```json { "last_check": "2026-05-20T10:00:00Z", "notificaciones_vistas": [1234567, 1234568, 1234569] } ``` Solo guardamos los IDs (`codNotificacionOrigen`) ya notificados. Diff: cualquier ID en la respuesta del PJ que no esté en esta lista = novedad. --- ## Comandos de Telegram - `/start` — muestra estado del bot - `/notif` — revisa notificaciones ahora (no espera al siguiente polling) - `/estado` — último check, cantidad de notificaciones activas - `/ayuda` — lista comandos - `/exp ` — detalle de expediente por número (ej: `/exp 198` o `/exp 198/2026`) - `/exp ` — busca expedientes por carátula (ej: `/exp rodríguez`) --- ## Formato de mensajes al abogado ### Notificación proactiva nueva ``` 📋 Nueva notificación judicial {caratula} Exp. {numero}/{anio} — {juzgado} Tipo: {descripcionActuacion} Fecha: {fecha formateada DD/MM/YYYY} ⚠️ Para acusar esta notificación entrá directamente en https://apps.csj.gov.py ``` ### Sin novedades (solo cuando el abogado lo pide con /notif) ``` ✅ Sin novedades desde las {hora} de hoy. Tenés {N} notificaciones pendientes en total. ``` --- ## Iniciativa agéntica — clasificación de cambios | Nivel | Tipo de cambio | Acción | |-------|---------------|--------| | 1 | Refactor interno, naming, código muerto | Ejecutar libremente | | 2 | Cambio visible en mensajes del bot o UX conversacional | Ejecutar y reportar | | 3 | Seguridad, credenciales, dependencias nuevas, scope del bot | **Consultar antes** | **Argumentos NO válidos para evitar consulta de Nivel 3:** - "Es una mejora de legibilidad" - "Es trivial de revertir" - "Resuelve un bug menor" --- ## Definition of Done — cada feature o fix - [ ] Happy path funciona en ejecución real (`python main.py`) - [ ] Credenciales no aparecen en logs (verificar con `LOG_LEVEL=DEBUG`) - [ ] Maneja desconexión del PJ con retry + mensaje claro al usuario - [ ] Maneja timeout de sesión: relogin automático transparente - [ ] Maneja datos vacíos (cero notificaciones, response inesperado) - [ ] `credential_scrubber()` usado en cualquier log con datos externos - [ ] Type hints completos en funciones nuevas o modificadas - [ ] Sin `print()` en código nuevo --- ## Alerta especial: fragilidad del cliente HTTP El `csj_client.py` es la parte más probable de romperse. Ante cualquier reporte de "el cliente funciona", verificar: - ¿Se probó con credenciales reales o con mocks? - ¿Los campos del JSON del PJ son los actuales o los del último test? - ¿El timeout fue suficiente para la red real? Ante cambio de schema del PJ: actualizar `csj_client.py` y documentar en `OBSERVATIONS.md` qué cambió y cuándo. --- ## Archivos del proyecto | Archivo | Propósito | |---------|-----------| | `CLAUDE.md` | Este archivo — reglas para Claude Code | | `CHANGELOG.md` | Historial de versiones — actualizar antes de cada release | | `DEPLOY.md` | Guía de despliegue en VPS: Docker, Nginx, SSL, DNS | | `OBSERVATIONS.md` | Hallazgos durante el desarrollo (schema real del PJ, etc.) | | `TECH_DEBT.md` | Deuda técnica anotada | | `migrations/` | Archivos `.sql` de schema — nunca editar los ya aplicados | | `data/pasante.db` | Base de datos SQLite (tenants, audit_log, invite_codes) | | `data/snapshot_*.json` | Estado del diff de notificaciones por tenant | --- ## Gestión de versiones ### Esquema: Semantic Versioning (semver 2.0) `MAJOR.MINOR.PATCH` — tags git con prefijo `v` (ej: `v0.3.1`). | Número | Incrementar cuando... | Ejemplos | |--------|----------------------|---------| | `MAJOR` | Cambio incompatible con versiones anteriores: migración de DB no retrocompatible, cambio de protocolo del PJ, rediseño de arquitectura | `v1.0.0` (migración a Postgres) | | `MINOR` | Feature nuevo sin romper lo existente: comando nuevo, integración nueva, mejora visible al usuario | `/recordatorio`, modo silencioso | | `PATCH` | Bug fix, security patch, mejora interna, documentación operacional | corrección de campo del PJ, hardening | ### Reglas que Claude Code sigue siempre 1. **Cada release requiere entrada en `CHANGELOG.md`** con fecha y secciones `Added / Changed / Fixed / Security` según corresponda. 2. **El tag se crea después del commit de changelog**, no antes. 3. **`[Unreleased]`** acumula todos los cambios hasta que Marcos decide liberar. 4. **Claude propone el número de versión** basándose en las reglas de arriba; Marcos lo aprueba antes de tagear. 5. **`git tag` y `git push --tags` los ejecuta Claude solo cuando Marcos lo confirma** — son acciones externas (modifican el remoto) que requieren aprobación explícita. 6. **Tags retroactivos** son válidos para dar historia a commits anteriores; se crean con `git tag -a vX.Y.Z `. ### Proceso de release (copy-paste) ```bash # 1. CHANGELOG.md ya actualizado (mover [Unreleased] → [X.Y.Z] con fecha) git add CHANGELOG.md git commit -m "chore: release vX.Y.Z" # 2. Tag anotado git tag -a vX.Y.Z -m "vX.Y.Z — resumen en una línea" # 3. Push incluyendo el tag git push origin main --tags ``` --- ## Cómo arrancar ```bash # Setup (una sola vez) pip install uv && uv sync cp .env.example .env # Completar .env: FERNET_KEY, TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID # Cifrar credenciales del PJ python scripts/cifrar_credenciales.py # Arrancar python main.py # Tests pytest pytest -k "poller" -v pytest --co -q # solo listar tests sin correr ``` --- ## Cómo migrar de v0 a v1 La v0 está diseñada para migración incremental: - `csj_client.py` → `app/scraper/` con el mismo contrato de métodos - `storage.py` → Postgres con el mismo interface - `config.py` → se expande con más settings - `telegram_bot.py` → `app/bot/channels/telegram/` - `poller.py` → `app/poller/` - `utils/sanitize.py` → se mantiene igual No es código desechable.