# Salkantay API

## Template de endpoints (CQRS)

Este proyecto usa arquitectura CQRS estricta para crear módulos. El template completo con todos los patrones, código base, convenciones y checklists está en `.claude/endpoint-template.md` (no versionado en git).

**Cuándo cargarlo automáticamente:** Leer `.claude/endpoint-template.md` antes de escribir cualquier archivo cuando el usuario pida:
- Crear un módulo nuevo o endpoint
- Crear cualquier capa: Controllers, DTOs, Domain, Application, Infrastructure, Tests
- Crear cualquier componente de la arquitectura: Aggregate, Criteria, Handler, Inserter, Updater, Deleter, Lister, Repository, Mother, etc.
- Preguntas sobre convenciones, patrones o estructura del proyecto

**Regla:** El template manda. Nunca crear archivos que no estén documentados en el template.

---

## Módulos implementados

Actualizar esta tabla al final de cada sesión cuando se complete un módulo o capa nueva.

| Módulo | Tabla | Controllers | Module | Notas |
|---|---|---|---|---|
| ProviderLanguage | `t_provider_language` | ✓ completo | ✓ completo | Módulo de referencia. Routes + Tests (5) incluidos. |
| CurrencyExchange | `t_currency_exchange` | ✓ completo | ✓ completo | TOP-LEVEL. Dos FKs a misma tabla (IdCurrency From/To). DECIMAL→NumericValueObject. Filtros opcionales en List con append(). Validación de par duplicado (igual o invertido From↔To, solo status≠0) en Insert/Update vía criteria `DuplicatedCurrencyExchange` + excepción 409 `CurrencyExchangeDuplicated`. |
| TypeFeature | `t_type_feature` | ✓ completo | ✓ completo | TOP-LEVEL. Sin FK. `ListTypeFeatureQuery`/`ListTypeFeature`/`TypeFeatureLister` extendidos a mano con filtro opcional `?array $ids` (patrón Currency) para soportar populate belongs-to desde otros módulos (ej. Feature). Generado con `tools/module-generator`. Routes + Tests (8) incluidos. |
| Feature | `t_feature` | ✓ completo | ✓ completo | TOP-LEVEL (FK `Id_TypeFeature` opcional en List). Boolean `Feature_IsExpirable`. Populate `TypeFeature` en List/Index usa `ListTypeFeatureQuery(ids: ...)` (ver nota TypeFeature). `ListFeatureQuery`/`ListFeature`/`FeatureLister` también extendidos con filtro opcional `?array $ids` para que módulos futuros (ProviderFeature, TourRequiredFeature) puedan poblar Feature por ids. **`ListFeatureQuery` `page`/`pageSize` ahora `?int=null`** y `ListFeature` saltea limit/offset con `if(isPaginated())` → populate por ids trae todo sin cap (reemplaza hack `pageSize:9999`). Generado con `tools/module-generator`. Routes + Tests (8) incluidos. |
| Language | `t_language` | — solo lectura | ✓ read-only | **Catálogo solo lectura** (sin Date/DateUpdate → sin Insert/Update/Delete/History). Construido a mano (el generador asume auditoría). Domain (aggregate+VOs+`IndexLanguage`/`ListLanguage`)+Infra+`ListLanguageQuery(action, ?array ids)` unpaginated+`IndexLanguageQuery`+Response(s). **Sin rutas HTTP propias** (las legacy viven en `routes/owner/language.php` con controller monolítico `Owner\Language`). Binding `LanguageRepository`→`Mysql` agregado en `routes/owner/language.php`. Existe para populate `Language` (belongs-to) desde ProviderLanguage. Prop `default`→attribute/prop `languageDefault` (evita keyword). |
| ProviderFeature | `t_provider_feature` | ✓ completo | ✓ completo | HIJO de Provider (`--child`, ruta anidada `/provider/{Id_Provider}/providerFeature`). 2 FKs: `Id_Provider` (padre) + `Id_Feature` (catálogo). Populate `Provider` y `Feature`. **DATETIME de negocio `DateStart`/`DateEnd`**: el generador los saca como `int` (solo mapea `DATE`, no `DATETIME`) → parcheados a mano a `DateTimeValueObject` + regla DTO `date_format:Y-m-d H:i:s`. `DateEnd` opcional en request pero **regla de negocio en `InsertProviderFeatureHandler`**: carga Feature vía `IndexFeatureQuery`; si `Feature_IsExpirable==2` y falta DateEnd → excepción 422 `ProviderFeatureDateEndRequired`; si no expirable y falta → `DateEnd=DateStart`. Regla solo en Insert (Update deja DateEnd opcional). Generado con `tools/module-generator`. Routes + Tests (8) incluidos. |
