# Handoff — module-generator (retomar idéntico)

> Estado al cerrar sesión 2026-06-13. Leer esto primero al volver.

## Qué se hizo

1. **Refactor heredoc → stubs** (completo, verificado byte-identical vs baselines `_b_*`).
2. **Fix Search opcional:** controllers List emiten `search: $dto->Search ?? ''` (DTO tiene `?string $Search`).
3. **Feature Populate FK-driven:** módulos con FK generan eager-load belongs-to en List + Index. Sin FK → handlers simples (sin boilerplate muerto).

## Populate — diseño clave

- Identidad derivada de la **COLUMNA**, no de la entidad → evita colisión doble-FK
  (`Id_Currency_From`/`Id_Currency_To` → `CurrencyFrom`/`CurrencyTo`, ambos entidad `Currency`).
- Closure-map + helpers `DataObject` / `DataUtils`.
- `DataUtils::findFromMap($map, $id, true)` → `true` = belongs-to (entidad única).
- **Assumption:** entidad target debe exponer `List{E}Query(action, ids)` → `{E}Responses`
  (convención catálogo, ej Currency). Caso real City: módulo `City` **aún NO existe** — al
  generar módulo con `Id_City` habrá que crear City o ajustar `getCity()` a mano.

## Archivos tocados

### templates.php (M)
- `listControllerFile()`: search nullable fix (listArgs + countArgs).
- Helpers nuevos tras `fkImports()`:
  - `fkPopulates($ctx)` → `[entity, rel, const, column, getter]` por FK.
  - `listPopulateFile` / `indexPopulateFile` → `populateVoFile($ctx, $layer)`.
  - `fkPopulateHandlerParts($ctx, $populateClass)` → `[closuresStr, gettersStr, imports[]]`.
- `buildFiles()`: si `!empty($fkF)` emite `List{M}Populate.php` + `Index{M}Populate.php`.
  Index builders pasan `$ctx`.
- `listQueryFile()`: si FK → param `public readonly string $populate = ''`.
- `listQueryHandlerFile()`: branch empty=`listQueryHandler` / FK=`listQueryHandler_populate`.
- `listDtoFile()`: si FK → regla `'Populate' => 'string'` + `public ?string $Populate`.
- `indexQueryHandlerFile($ctx)`: branch `indexQueryHandler` / `indexQueryHandler_populate`.
- `indexControllerFile($ctx)`: branch `indexController` / `indexController_populate`.
- `indexDtoFile($ctx)`: branch `indexDto` / `indexDto_populate`.

### Stubs nuevos (untracked ??)
- `listPopulate.stub`, `indexPopulate.stub` — VO extiende `PopulateValueObject`, `{{ consts }}` + `{{ values }}`.
- `listQueryHandler_populate.stub` — QueryBus inyectado, closure-map, getters.
- `indexQueryHandler_populate.stub` — variante single-entity (`$responses=[...]; return $responses[0];`).
- `indexController_populate.stub`, `indexDto_populate.stub` — pasan `populate: $dto->Populate ?? ''`.
- (También untracked: `*ControllerTest.stub` — tests, de antes.)

### CLAUDE.md
- Sección "### Application — Populate (eager-load de FK)" entre Response DTOs e Infrastructure.

## Verificación hecha

- Regen District (`Id_City` FK) → populate correcto List+Index, lint OK.
- Regen Widget (sin FK) → sin archivos populate, handlers simples, lint OK.

## Sesión 2026-07-08 — fixes generador (ProviderFeature)

Aprendido generando `ProviderFeature` (HIJO, 2 FKs: Provider + Feature, campos DATETIME de negocio):

1. **`DATETIME`/`TIMESTAMP` → `ROLE_DATETIME`** (`generate-module.php`). Antes `DATETIME` no tenía rama → caía a `int`. Ahora:
   - `voFile` → `DateTimeValueObject`.
   - `dtoParts` → regla `date_format:Y-m-d H:i:s` (insert `required|`, update sin prefijo).
   - `randomFor` (mother) → `new {VO}({VO}::nowPeru()->value())`.
   - `phpType = string`. (Solo `_Date`/`_DateUpdate` siguen siendo roles fijos de auditoría.)
2. **`ROLE_DATE_PLAIN` (DATE) sin regla DTO** → agregada `date_format:Y-m-d`. Antes emitía param sin validación.
3. **Paginación unpaginated** (para populate-by-ids sin hack `9999`):
   - `listQueryFile`: `page`/`pageSize` → `?int = null`; params siguientes (FK filters, `search`) reciben `= null`.
   - `listCriteria_hijo.stub` / `listCriteria_top.stub`: `limit`/`offset` con guard `$queryPaginate->isPaginated() ? ... : null`. `LaravelCriteriaMatcher` usa `if($limit)` → null = sin LIMIT = todo.
   - Módulo target populable con `List{E}Query(action:'all', ids:[...])` sin pasar page/pageSize.
4. **Imports FK muertos en criteria HIJO** (`listCriteriaFile`): List/ListCount HIJO solo filtran por parent → solo importan `parentFk`. Antes importaban todas las FK Id (ej. `IdFeature` sin usar).

**Verificado:** regen throwaway `DemoThing` (HIJO, Provider+Feature, DATETIME) → StartAt=`DateTimeValueObject`, DTO `date_format`, ListQuery `?int=null`, criteria guard `isPaginated()`, sin import `IdFeature`. Lint OK. Módulo throwaway borrado + registries revertidos.

**Nota:** Feature module (`ListFeatureQuery`/`ListFeature`) y `ListProviderFeature` fueron parcheados a mano igual (page/pageSize `?int=null` + guard) porque ya existían pre-fix.

## Pendiente / decisiones abiertas

- **Sin commit.** Nada commiteado.
- `_b_*` dirs (baselines pre-refactor) siguen en disco, **git-tracked**, ya marcados `D` en
  working tree desde antes. Decisión usuario: ¿borrarlos (refactor ya verificado) o no?
- Crear módulo `City` real (o ajustar `getCity()`) cuando se genere módulo con `Id_City`.

## Comandos útiles

```bash
# generar módulo (lee CREATE TABLE de stdin/clipboard)
php tools/module-generator/generate-module.php

# ver stubs populate
ls tools/module-generator/stubs/ | grep -i populate
```
