# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Descripción del Proyecto

Sistema de procesamiento automático de facturas Mercedes-Benz en PDF. Extrae, clasifica y agrupa facturas basándose en metadatos extraídos de regiones específicas de cada página. Genera códigos de barras Code128, calcula estadísticas de zonas de Correos y produce un ZIP final para envío.

## Comandos de Ejecución

### Script Principal
```bash
python process_invoices.py                           # Procesa todos los PDFs de carpeta 'input'
python process_invoices.py --ftp                     # Procesa y sube por FTP
python process_invoices.py --ftp --email             # Procesa, sube por FTP y envía email
python process_invoices.py <pdf_path>                # Procesa un PDF específico
python process_invoices.py <pdf_path> --ftp --email  # Procesa, sube por FTP y envía email
```

Si no se especifica un PDF, el script busca automáticamente en `input/` y mueve los archivos procesados a `data/XXXX/origin/`.

**Flags:**
- `--ftp`: Sube el ZIP al servidor FTP (credenciales en `.env`)
- `--email`: Envía notificación por email con estadísticas adjuntas

### Script de Sincronización con API (sync_histories.py)
```bash
python3 sync_histories.py                # Sincroniza directorios de /data con Histories
python3 sync_histories.py --dry-run      # Simula sin hacer cambios reales
python3 sync_histories.py --cleanup      # Sincroniza y borra directorios automáticamente
```

**Descripción:**
Sincroniza todos los directorios en `/data/YYYYMMDD_HHMMSS` con una API remota, creando/actualizando "Histories" (registros de procesamiento). Cada directorio corresponde a una History que contiene:
- **input_files**: Ficheros de entrada desde `data/XXXX/origin/`
- **output_files**: Ficheros de salida desde `data/XXXX/output/`

**Configuración:**
El script usa **rutas relativas** (relativas al directorio donde se ejecuta):
```python
# En sync_histories.py
API_BASE_URL = "https://mercedesbenz.fresbe.com"  # Producción
PROJECT_DIR = Path(__file__).parent.resolve()       # Directorio del script
DATA_DIR = PROJECT_DIR / "data"                     # Ruta relativa
LOGS_DIR = PROJECT_DIR / "logs"                     # Ruta relativa
CACHE_FILE = PROJECT_DIR / ".sync_cache.json"       # Caché MD5
CREDENTIALS = {
    "email": "javiercabellos@fresbe.com",
    "password": "password",
    "device_name": "daimler-pipeline-sync"
}
```

**Funcionamiento:**
1. Se autentica en la API con `POST /api/login`
2. Obtiene lista de Histories existentes con `GET /api/histories`
3. Para cada directorio en `/data`:
   - Calcula hash MD5 de cada fichero y compara con caché
   - Si detecta cambios, sincroniza ficheros con la API
   - Si no hay cambios, omite la sincronización (optimización)
   - Si la History no existe, la crea con `POST /api/histories`
   - Si existe, actualiza ficheros con `PUT /api/histories/{id}`
4. Valida integridad de ficheros sincronizados
5. Si `--cleanup` está activo y todo fue exitoso:
   - Borra automáticamente los directorios de `/data` (sin confirmación)
   - Loguea cada directorio borrado con tamaño
6. Muestra estadísticas detalladas de ejecución

**Mejoras Implementadas:**
- ✅ **Detección de cambios**: Usa caché de hashes MD5 (`.sync_cache.json`) para solo sincronizar ficheros modificados
- ✅ **Logging detallado**: Registra todas las operaciones en `logs/sync_YYYYMMDD.log` (niveles DEBUG/INFO)
- ✅ **Reintentos automáticos**: 3 intentos con backoff exponencial ante fallos de red
- ✅ **Modo dry-run**: Ver qué se haría sin hacer cambios (`--dry-run`)
- ✅ **Validación de integridad**: Verifica checksums MD5 después de sincronizar
- ✅ **Estadísticas**: Muestra tiempo, tamaño, velocidad, ficheros omitidos
- ✅ **Cleanup automático**: Borra directorios después de sincronizar exitosamente (`--cleanup`, ideal para cron)

**Argumentos:**
- `--dry-run`: Modo simulación (no hace cambios reales)
- `--cleanup`: Borra directorios después de sincronizar exitosamente (automático, sin confirmación)

**Archivos Generados:**
- `.sync_cache.json`: Caché de hashes para detectar cambios (se actualiza automáticamente)
- `logs/sync_YYYYMMDD.log`: Log detallado de cada ejecución

**Nota:** La API genera nombres aleatorios para los ficheros almacenados en el servidor (por seguridad), pero mantiene referencia a los nombres originales.

### Script de Descarga desde Inbox (download_inbox.py)
```bash
python3 download_inbox.py                            # Descarga documentos con 90min mínimo en inbox
python3 download_inbox.py --dry-run                  # Simula sin descargar realmente
python3 download_inbox.py --no-delete                # Descarga pero no borra de la API
python3 download_inbox.py --min-minutes 120          # Especifica tiempo mínimo personalizado
python3 download_inbox.py --endpoint /api/inbox      # Especifica endpoint custom
```

**Descripción:**
Descarga documentos desde el inbox de la API (solo si han estado >90 minutos), los guarda en `/input`, verifica integridad y los borra de la API. Usa rutas relativas (relativas a la ubicación del script).

**Funcionamiento:**
1. Se autentica en la API
2. Consulta el endpoint del inbox
3. Para cada documento:
   - Verifica que haya estado mínimo `--min-minutes` (default: 90) en el inbox
   - Si es muy reciente, lo omite hasta que cumpla el tiempo
   - Si cumple, descarga el documento
   - Verifica integridad (hash MD5 si está disponible)
   - Guarda en `input/`
   - Si hash OK, borra de la API
4. Muestra estadísticas y logs en `logs/download_YYYYMMDD.log`

**Características:**
- ✅ Filtro temporal: solo descarga documentos antiguos (evita descargas prematuras)
- ✅ Modo dry-run para simular sin cambios
- ✅ Validación de integridad con hashes
- ✅ Logging detallado (DEBUG+INFO)
- ✅ Reintentos automáticos ante fallos (3 intentos con backoff exponencial)
- ✅ Opcional: no borrar de la API con `--no-delete` (útil para diagnóstico)
- ✅ Rutas relativas: se adapta a cualquier ubicación

**Argumentos:**
- `--dry-run`: Modo simulación (no hace cambios reales)
- `--min-minutes N`: Mínimos minutos desde subida para descargar (default: 90)
- `--no-delete`: Descarga pero deja documentos en la API
- `--endpoint PATH`: Endpoint custom del inbox (default: `/api/inbox`)

**Importante:**
Antes de usar en producción, verifica el endpoint correcto y prueba:
```bash
python3 download_inbox.py --dry-run                    # Ver qué haría
python3 download_inbox.py --dry-run --min-minutes 1   # Probar con tiempo mínimo bajo
```

### Script de Utilidad (tools/extract_region.py)
```bash
python tools/extract_region.py extract <pdf> <page> <x0> <y0> <x1> <y1>  # Extraer de región
python tools/extract_region.py find <pdf> <texto> [page]                 # Buscar coordenadas
python tools/extract_region.py dims <pdf>                                # Ver dimensiones
python tools/extract_region.py words <pdf> [page]                        # Listar palabras
```

### Herramienta Web (tools/)
Abrir `tools/GUI_pdf_extractor.html` en navegador para definir coordenadas visualmente. Permite seleccionar regiones arrastrando sobre el PDF y exportar como código Python.

### Instalación de Dependencias
```bash
pip3 install -r requirements.txt
cp .env.example .env  # Configurar credenciales
```

## Flujo de Integración Completo

El sistema completo funciona en 3 fases integradas:

```
FASE 1: DESCARGA (download_inbox.py)
  API Inbox → download_inbox.py → /input/*.pdf
  (Espera 90+ minutos antes de descargar)

FASE 2: PROCESAMIENTO (process_invoices.py)
  /input/*.pdf → Step 1-7 → data/YYYYMMDD_HHMMSS/
  (Extrae, agrupa, genera ZIP, FTP opcional, email opcional)

FASE 3: SINCRONIZACIÓN (sync_histories.py)
  data/YYYYMMDD_HHMMSS/ → API Histories → cleanup automático
  (Sube con --cleanup para borrar directorios tras sincronizar)
```

**Caso de uso típico (cron job):**
```bash
# 1. Descarga documentos del inbox (cada hora)
0 * * * * cd /path/to/daimler-pipeline && python3 download_inbox.py

# 2. Procesa PDFs de input (cada 6 horas)
0 */6 * * * cd /path/to/daimler-pipeline && python3 process_invoices.py --ftp --email

# 3. Sincroniza con API y limpia (después del procesamiento)
15 */6 * * * cd /path/to/daimler-pipeline && python3 sync_histories.py --cleanup
```

---

## Arquitectura de process_invoices.py

Pipeline de 7 pasos:

1. **Step 1 - Extracción** (`step1_extract_pages`): Separa páginas individuales, extrae metadatos, mueve región definida en `REGION_MOVER` y añade código de barras Code128.
   - Salida: `{cp}_{pagina}_{factura}.pdf`

2. **Step 2 - Agrupación** (`step2_merge_invoices`): Combina páginas de la misma factura ordenadas por número de página.
   - Salida: `{cp}_{num_paginas}_{factura}.pdf`

3. **Step 3 - Unificación** (`step3_unify_by_page_count`): Agrupa facturas por cantidad de páginas, ordenadas por código postal.
   - Salida: `facturas_{n}paginas.pdf`

4. **Step 4 - Estadísticas** (`CorreosHelper.print_stats`): Calcula y exporta estadísticas de zonas de Correos basándose en códigos postales.
   - Salida: `output/estadisticas_correos.txt`

5. **Step 5 - ZIP**: Empaqueta los PDFs de step3 en un archivo comprimido.
   - Salida: `output/DAIMLERTRUCKS_{execution_id}.zip`

6. **Step 6 - FTP** (opcional, con `--ftp`): Sube el ZIP al servidor FTP en carpeta `DAIMLER_TRUCKS`.
   - Salida: `output/subida_ftp.txt`

7. **Step 7 - Email** (opcional, con `--email`): Envía notificación por email con estadísticas adjuntas.
   - Asunto: `[DAIMLER] {execution_id}`
   - Indica si FTP fue exitoso o fallido

## Componentes Técnicos Internos

### Clase CorreosHelper

Calcula zonas de envío de Correos según código postal español. Usa lógica de reglas jerarquizadas:

| Zona | Descripción | Patrón |
|------|-------------|--------|
| 1 | Nacional Local | CP comienza con 280 |
| 2 | Destino 1 Canarias | CP 35|38 (primeros 3) OR en lista especial |
| 3 | Destino 1 Ceuta | CP comienza con 51 |
| 4 | Destino 1 Melilla | CP comienza con 52 |
| 5 | Destino 2 Canarias | CP comienza con 35 o 38 |
| 6 | Destino 2 Andorra | CP comienza con AD |
| 7 | Destino 1 | CP en lista especial OR carácter 2=0 (xx0xx) |
| 8 | Destino 2 | Defecto (cualquier otro) |
| 9 | Internacional | CP > 5 caracteres |

**Métodos clave:**
- `compute_zone(zip_code)`: Retorna ID de zona (1-9)
- `analyze_step2(step2_dir)`: Analiza step2 y agrupa por zona
- `print_stats(step2_dir)`: Genera tabla de estadísticas:
  - Filas: zonas de Correos (ID 1-9)
  - Columnas: cantidad de páginas (1p, 2p, 3p, etc.)
  - Valores: cantidad de facturas, porcentajes

### Transformaciones de PDF (Step 1)

Cada página pasa por 3 transformaciones superpuestas:

**1. Movimiento de Región (create_region_move_pdf)**
- Lee región origen como imagen con PyMuPDF (3x zoom para calidad)
- Tapa región origen con rectángulo blanco (reportlab)
- Redibuja imagen en región destino con reportlab
- Maneja conversión de coordenadas:
  - Entrada: coordenadas visuales (origen arriba-izquierda)
  - PyMuPDF: usa coords visuales nativas
  - reportlab: convierte a coords PDF (origen abajo-izquierda): `y_pdf = page_height - y_visual`

**2. Código de Barras Code128 (create_barcode_pdf)**
- Genera código de barras del número de factura con `python-barcode`
- Rota 90 grados (vertical) con Pillow
- Posición: margen derecho superior (x: page_width-38, y: page_height-220)
- Dimensiones: 26×70 puntos
- Se superpone sobre la página original

**3. Extracción de Metadatos (extract_page_data)**
- Usa coordenadas COORDS para extraer:
  - `numero_factura`: número o "unknown"
  - `pagina`: "1/3", "2/3", etc. (usa regex para sacar el 1, 2, etc.)
  - `codigo_postal`: busca exactamente 5 dígitos (`\b(\d{5})\b`)

### Convención de Nombres de Archivo

Tres patrones de nombre según el paso:

**Step 1:** `{cp}_{pagina}_{numero_factura}.pdf`
- Ejemplo: `28001_1_3_F12345.pdf`
- Significado: CP 28001, página 1 (de 3), factura F12345

**Step 2:** `{cp}_{num_paginas}_{numero_factura}.pdf`
- Ejemplo: `28001_3_F12345.pdf`
- Significado: CP 28001, 3 páginas combinadas, factura F12345

**Step 3:** `facturas_{n}paginas.pdf`
- Ejemplo: `facturas_3paginas.pdf`
- Significado: todas las facturas de exactamente 3 páginas (ordenadas por CP)

## Sistema de Coordenadas

**Importante:** El proyecto usa **coordenadas visuales** (origen arriba-izquierda, Y aumenta hacia abajo):

```python
# Todas las coordenadas en sistema visual:
# (x0, y0, x1, y1) donde (x0,y0) = esquina superior-izquierda
COORDS = {
    "numero_factura": (460, 215, 520, 235),   # Extracción con pdfplumber
    "pagina": (464, 252, 478, 263),
    "codigo_postal": (50, 155, 95, 200),
}

REGION_MOVER = {
    "origen": (402.5, 122.0, 575.5, 266.0),   # Región a mover
    "destino": (384.5, 130.0, 560.5, 272.0),  # Destino de la región
}
```

**Librerías y sus sistemas:**
| Librería | Entrada | Salida | Sistema |
|----------|---------|--------|---------|
| pdfplumber | PDF | Texto, coords | Visual (arriba-izquierda) |
| PyMuPDF (fitz) | PDF | Imagen | Visual (arriba-izquierda) |
| reportlab | Canvas | PDF | PDF (abajo-izquierda) |
| pypdf | PDF | PDF | PDF (abajo-izquierda) |

**Conversión automática:**
El script maneja la conversión automáticamente en `create_region_move_pdf()`:
```python
# Reportlab (PDF coords): y_pdf = page_height - y_visual - altura
y_pdf = page_height - origen[3]  # origen[3] es y1 visual
```

La herramienta web `tools/GUI_pdf_extractor.html` genera coordenadas visuales automáticamente.

## Estructura de Salida

```
data/YYYYMMDD_HHMMSS/
├── origin/  # PDFs originales (solo si se procesa desde carpeta input)
├── step1/   # Páginas individuales con código de barras y región movida
├── step2/   # Facturas completas (multi-página combinadas)
├── step3/   # PDFs unificados por número de páginas
└── output/
    ├── estadisticas_correos.txt   # Estadísticas por zona y nº páginas
    ├── subida_ftp.txt             # Confirmación de subida FTP
    └── DAIMLERTRUCKS_*.zip        # ZIP con PDFs de step3
```

## Librerías Principales

- **pdfplumber**: Extracción de texto y regiones → Step 1 `extract_page_data()`
- **pypdf**: Merge de PDFs → Step 2 y 3 `merge_pdfs()`
- **PyMuPDF (fitz)**: Extracción de región como imagen → Step 1 `create_region_move_pdf()`
- **reportlab**: Overlay de región movida y código de barras → Step 1
- **python-barcode**: Generación de Code128 → Step 1 `create_barcode_pdf()`
- **Pillow**: Manipulación de imágenes (rotación barcode) → Step 1
- **mailjet-rest**: Envío de emails con adjuntos → Step 7 (opcional)

## API Remota - Endpoints de Histories

Documentación de endpoints usados por `sync_histories.py`:

| Acción | Método | Ruta | Autenticación |
|--------|--------|------|----------------|
| Login | POST | `/api/login` | No requerida |
| Listar Histories | GET | `/api/histories` | Bearer token |
| Ver una History | GET | `/api/histories/{id}` | Bearer token |
| Crear History | POST | `/api/histories` | Bearer token |
| Actualizar History | PUT | `/api/histories/{id}` | Bearer token |
| Eliminar History | DELETE | `/api/histories/{id}` | Bearer token |

**Endpoint de Login:**
```json
POST /api/login
{
  "email": "usuario@ejemplo.com",
  "password": "contraseña",
  "device_name": "nombre-del-dispositivo"
}

Respuesta:
{
  "token": "1|abc123...",
  "user": {
    "id": 1,
    "name": "Nombre Usuario",
    "email": "usuario@ejemplo.com",
    "role": "admin"
  }
}
```

**Crear/Actualizar History con ficheros:**
- Usar `multipart/form-data` para enviar ficheros
- Campos: `name`, `description`, `input_files[]`, `output_files[]`
- Header requerido: `Authorization: Bearer {token}`
- Header recomendado: `Accept: application/json`
- Límites de servidor (ajustables):
  - `upload_max_filesize`: Máximo tamaño por fichero (ej: 100M)
  - `post_max_size`: Máximo tamaño total de POST (ej: 100M)

## Configuración (.env)

El archivo `.env` debe contener credenciales para FTP y Email:

```bash
# FTP (para --ftp flag en process_invoices.py)
FTP_HOST=ftp.ejemplo.com
FTP_USER=usuario_ftp
FTP_PASS=contraseña_ftp

# Email (para --email flag en process_invoices.py)
EMAIL_FROM=no-reply@ejemplo.com
MAILJET_API_KEY=tu_api_key_mailjet
MAILJET_SECRET_KEY=tu_secret_key_mailjet
```

Crear desde plantilla: `cp .env.example .env` y completar valores.

## Troubleshooting

### sync_histories.py

**Error: "Sin respuesta" en autenticación**
- Ejecuta diagnóstico: `python3 test_api.py`
- Verifica conectividad: `curl -I https://mercedesbenz.fresbe.com/api/login`
- Revisa que las credenciales sean correctas en el script
- Verifica que el servidor está disponible

**Error 500 al crear History con ficheros**
- Problema en la API Laravel al procesar uploads
- En el servidor Laravel, revisa logs: `tail -f storage/logs/laravel.log`
- Verifica permisos de carpetas: `chmod -R 755 storage/app/ && chmod -R 755 bootstrap/cache/`
- Verifica que existan las carpetas de almacenamiento: `ls -la storage/app/`

**El script omite ficheros (no sincroniza)**
- Comportamiento normal: usa caché de hashes MD5 para evitar re-sincronizaciones
- Los ficheros se marcan como `[SIN CAMBIOS]` cuando su hash coincide con el caché
- Para forzar sincronización de todos los ficheros: `rm .sync_cache.json && python3 sync_histories.py`
- Para ver detalle completo: revisa `logs/sync_YYYYMMDD.log`

**Logs detallados**
- Toda operación se registra en: `logs/sync_YYYYMMDD.log`
- Niveles: DEBUG (verbose), INFO (normal)
- Útil para auditoría y debugging

### process_invoices.py

**Error con FTP**
- Verifica credenciales en `.env`
- Prueba conectividad manual: `ftp -u ftp://usuario:password@servidor.com`
- Revisa logs en `data/YYYYMMDD_HHMMSS/output/subida_ftp.txt`

**Error con Email**
- Verifica credenciales de Mailjet en `.env`
- Revisa que tengas variables: `MAILJET_API_KEY`, `MAILJET_SECRET_KEY`, `MAILJET_SENDER_EMAIL`, `MAILJET_RECIPIENT_EMAIL`
- Usa `--email` sin `--ftp` para probar el email sin FTP
- Revisa logs de consola para detalles del envío

**PDF no se procesa o sale "unknown"**
- Los metadatos no se extrajeron correctamente
- Verifica que las regiones en `COORDS` coinciden con el PDF:
  - Usa herramienta web: `tools/GUI_pdf_extractor.html`
  - O extrae manualmente: `python tools/extract_region.py extract <pdf> <page> x0 y0 x1 y1`
- Revisa los PDFs en `step1/` para ver si el código de barras y movimiento se aplicaron

**Código de barras incorrecto**
- Verifica que el número de factura se extrae correctamente en `extract_number_from_region()`
- Usa `python tools/extract_region.py extract <pdf> 0 460 215 520 235` para probar extracción

**Región movida no se ve**
- Las coordenadas REGION_MOVER podrían ser incorrectas
- Prueba con zoom visual: `tools/GUI_pdf_extractor.html`
- Ajusta origen/destino en el CLAUDE.md y re-ejecuta

**Estadísticas vacías o incorrectas**
- Verifica que los códigos postales se extrajeron correctamente (deben ser 5 dígitos exactos)
- Revisa `step2/` para ver si los PDFs tienen los CP correctos en el nombre

### download_inbox.py

**Error: Documentos omitidos (⏳ tiempo mínimo)**
- Comportamiento normal: el script espera 90 minutos (ajustable con `--min-minutes`)
- Para probar con documentos nuevos: `python3 download_inbox.py --min-minutes 0`
- Para uso en producción: mantén el default de 90 minutos

**Error: No encuentra documentos en el inbox**
- Verifica el endpoint correcto: `python3 download_inbox.py --dry-run --endpoint /api/inbox`
- Ejecuta diagnóstico: `python3 test_api.py`
- Revisa que haya documentos en la API: `curl -H "Authorization: Bearer TOKEN" https://api.com/api/inbox`
- Revisa logs: `logs/download_YYYYMMDD.log`

**Error: Estructura de respuesta inesperada**
- El script maneja: `{data: [...]}, {documents: [...]}, {inbox: [...]}` o array directo
- Si hay otra estructura, revisa `get_inbox_documents()` y ajusta el parsing
- Usa `--dry-run` primero para ver la estructura

**Hash no coincide**
- El documento se borra del local si el hash no coincide
- Revisa logs para detalles: `logs/download_YYYYMMDD.log`
- Usa `--no-delete` para descargar sin borrar mientras diagnosticas
- Verifica conectividad: `python3 download_inbox.py --dry-run`

---

## Desarrollo y Testing

### Testing de Scripts

**Modo dry-run (seguro para testing):**
```bash
# Ver qué haría process_invoices sin cambios
python process_invoices.py /ruta/test.pdf 2>&1 | head -20

# Ver qué descargaría download_inbox
python3 download_inbox.py --dry-run

# Ver qué sincronizaría sync_histories
python3 sync_histories.py --dry-run
```

**Testing de extracción de coordenadas:**
```bash
# Extraer región específica
python tools/extract_region.py extract /ruta/test.pdf 0 460 215 520 235

# Ver dimensiones del PDF
python tools/extract_region.py dims /ruta/test.pdf

# Buscar texto específico
python tools/extract_region.py find /ruta/test.pdf "FACTURA" 0

# Listar todas las palabras en página 0
python tools/extract_region.py words /ruta/test.pdf 0
```

**Testing de API:**
```bash
# Ejecutar todos los tests API (login, GET, POST simple, POST con archivo)
python3 test_api.py

# Ejecutar en verbose mode
python3 test_api.py 2>&1 | tee test_output.log
```

### Archivos de Prueba Recomendados

- Mantén un PDF de prueba en `input/test.pdf` con las regiones COORDS correctas
- Usa `--dry-run` para todos los scripts nuevos antes de ejecutar en serio
- Los logs en `logs/` contienen información detallada de cada ejecución (DEBUG+INFO)

### Estructura de Caché y Logs

```
proyecto/
├── .sync_cache.json              # Caché MD5 (generado automáticamente)
├── logs/
│   ├── download_20260521.log     # Descargas (diario)
│   ├── sync_20260521.log         # Sincronizaciones (diario)
│   └── process_20260521.log      # (si se agrega logging a process_invoices)
└── data/
    └── YYYYMMDD_HHMMSS/          # Directorios de ejecución
```

Los logs se crean automáticamente en `logs/` con nombre fechado. Revisa los logs más recientes para diagnosticar problemas:
```bash
tail -f logs/download_$(date +%Y%m%d).log    # Monitor en tiempo real
tail -50 logs/sync_$(date +%Y%m%d).log       # Últimas 50 líneas
```

### Desarrollo de Nuevas Funcionalidades

**Patrón de integración:**
1. Los scripts principales usan rutas relativas: `Path(__file__).parent.resolve()`
2. Evita hardcodear rutas absolutas
3. Usa logging para debugging (`.setLevel(logging.DEBUG)` existe en algunos scripts)
4. Implementa `--dry-run` en nuevos features para seguridad
5. Actualiza este CLAUDE.md con cualquier cambio arquitectónico
