# scan-core — Plan de Refactorización
Limpieza de código paja y duplicado. Sin cambios funcionales, solo organización interna.
---
## 1. Eliminar `cv/warp.rs` (DUPLICADO)
El módulo `cv/warp.rs` (158 líneas) reimplementa la transformación de perspectiva que ya existe en `transform/perspective.rs` + `image_utils::warp_perspective`.
| `cv/warp.rs` | `warp()`, `get_perspective_transform()`, `square_2_quad()` |
| `cv/mod.rs` | `pub mod warp` |
**Verificar** que nadie importa `cv::warp` — actualmente `aruco/detector.rs` usa `cv::warp::warp()` y debe migrar a `image_utils::warp_perspective()` + `PerspectiveTransform`.
---
## 2. Eliminar `cv/grayscale.rs` (TRIVIAL)
```rust
// grayscale.rs:5-6 — esto envuelve una función de 1 línea del crate image
pub fn grayscale(img: &DynamicImage) -> GrayImage {
img.to_luma8()
}
```
- `grayscale()` → reemplazar por `img.to_luma8()` directo
- `grayscale_rgba()` → mover a `cv/mod.rs` como helper si alguien la usa (verificar)
- Eliminar el archivo y el `pub mod grayscale` de `cv/mod.rs`
---
## 3. Mover `otsu_threshold` y `global_threshold` de `aruco/detector.rs` a `cv/threshold.rs`
Actualmente `aruco/detector.rs` tiene implementaciones privadas de:
- `otsu_threshold()` (35 líneas) — debería ser público en `cv/threshold.rs`
- `global_threshold()` (8 líneas) — debería ser público en `cv/threshold.rs`
**Acción**: Mover ambas funciones a `cv/threshold.rs` como `pub fn` y hacer que `aruco/detector.rs` las importe con `use crate::cv::threshold::{otsu_threshold, global_threshold}`.
---
## 4. Mover `encode_debug_image` y `to_png_bytes` a un solo lugar
- `aruco/detector.rs:343` tiene `encode_debug_image()` que llama a `image_utils::to_png_bytes()`
- `invisible/detector.rs` tiene su propia versión inline
**Acción**: Hacer una sola función pública `image_utils::encode_debug_image()` y eliminar las copias locales.
---
## 5. Unificar `warp` entre ArUco e Invisible
| **ArUco** | `cv::warp::warp(&gray, &pts, size)` — usa `square_2_quad` |
| **Invisible** | `image_utils::warp_perspective(&gray, &pt, w, h)` — usa `PerspectiveTransform` |
**Acción**: Ambos deben usar `PerspectiveTransform` + `image_utils::warp_perspective()`:
```rust
// En aruco/detector.rs, reemplazar:
let cropped = cv::warp::warp(&gray, &pts, size);
// Por:
let pt = PerspectiveTransform::new(&pts, &dst_pts)?;
let cropped = image_utils::warp_perspective(&gray, &pt, w as u32, h as u32);
```
---
## 6. Limpiar `cv/mod.rs`
Eliminar:
- `pub mod warp`
- `pub mod grayscale`
- `to_gray_image()` — usa `GrayImage::from_raw()` directo
Resultado esperado de `cv/mod.rs`:
```rust
pub mod threshold;
pub mod contours;
pub mod geometry;
pub mod ncc;
pub mod clahe;
pub mod canny;
```
---
## 7. Compatibilidad con `skeleton-scanner-exams`
Verificar que el `ScanResult` serializado incluya los campos que `Scanner.jsx` espera:
| `success` | ✅ | ✅ Existe |
| `croppedImage` / `cropped_image` | ✅ | ✅ Existe (base64 PNG) |
| `debugImage` / `debug_image` | ✅ | ✅ Existe |
| `markedImage` / `marked_image` | ✅ | ✅ Existe |
| `boundingBox` | ✅ | ⚠️ No existe — agregar alias de `corners` |
| `detectedMarkers` | ✅ | ✅ Existe en ArUco |
| `match_score` | ✅ | ✅ Existe en Invisible |
**Acción**: Agregar campo `bounding_box` como alias de `corners` en `ScanResult` para que el skeleton no tenga que adaptar nombres.
---
## Resumen de archivos afectados
| **ELIMINAR** | `cv/warp.rs` |
| **ELIMINAR** | `cv/grayscale.rs` |
| **MODIFICAR** | `cv/mod.rs` — quitar mods eliminados |
| **MODIFICAR** | `cv/threshold.rs` — agregar `otsu_threshold`, `global_threshold` |
| **MODIFICAR** | `aruco/detector.rs` — usar imports de cv + PerspectiveTransform |
| **MODIFICAR** | `image_utils.rs` — agregar `encode_debug_image` público |
| **MODIFICAR** | `invisible/detector.rs` — usar `image_utils::encode_debug_image` |
| **MODIFICAR** | `lib.rs` — agregar `bounding_box` a ScanResult |
## Verificación
```bash
cargo build --release
cargo run --release -- scan --input test.jpg --mode aruco4x4 --json
cargo run --release -- scan --input test.jpg --mode invisible --json
```