# Tasks: General CRM Backlog Fixes

**Input**: Design documents from `specs/002-general-backlog-fixes/`

**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/, quickstart.md (all present)

**Tests**: No automated test suite exists in this codebase (Constitution Principle VIII), and none was requested in the spec. Each user story ends with a manual-verification task against the matching section of `quickstart.md` instead of automated test tasks.

**Organization**: Tasks are grouped by user story (from `spec.md`) to enable independent implementation and verification of each story. All paths are relative to `sistema/new/` unless otherwise noted.

## Format: `[ID] [P?] [Story] Description`

- **[P]**: Can run in parallel (different files, no dependency on an incomplete task)
- **[Story]**: Which user story this task belongs to (US1–US5)

## Phase 1: Setup

**Purpose**: Confirm the environment is ready to work against. No new project scaffolding, dependencies, or tooling — this feature only edits existing files (plus a small number of new one-off files consistent with existing conventions — see US4/US5 below).

- [ ] T001 Confirm the local `sistema/new` stack is running under Docker Compose and reachable, per `quickstart.md` Prerequisites
- [ ] T002 [P] Confirm (or create) a dev `Empresa新` + `Profissional新` test record that can be freely edited, for manual verification across all 5 user stories, per `quickstart.md` Prerequisites

---

## Phase 2: Foundational

**Purpose**: Blocking prerequisites shared by every user story.

**None.** Each of the 5 user stories in this feature is independently scoped to its own files (per `spec.md`'s Independent Test criteria and `plan.md`'s Project Structure) — there is no shared schema change, shared service, or shared config that all five depend on. Any DB schema work is scoped to the one story that needs it (US4, US5) and is listed as the first tasks of that story's own phase.

**Checkpoint**: Nothing to complete here — proceed directly to any user story phase.

---

## Phase 3: User Story 1 - Log a non-effective interaction without a Profissional (Priority: P1) 🎯 MVP

**Goal**: Let a "Não Efetivo" interaction save with zero Profissionais selected, while still requiring at least one for "Efetivo" interactions, and display correctly afterward.

**Independent Test**: Open the interaction form, mark "Não Efetivo", leave "Profissionais" empty, save — the interaction is created, linked to zero Profissionais, and displays correctly in the company's history.

### Implementation for User Story 1

- [X] T003 [P] [US1] Remove the hardcoded `required` attribute from `<select multiple name="profissionais">` in `pages/interacoes/interacoes.php`
- [X] T004 [P] [US1] Remove the hardcoded `required` attribute from `<select multiple name="profissionais">` in `pages/interacoes/sucesso.php`
- [X] T005 [P] [US1] In the `$('input[name="efetividade"]').change(...)` handler in `includes/js/interacoes.js` (~lines 366-382), add `inputRequired('#interacao-input-profissionais', <true when efetividade==1>)` alongside the existing titulo/descricao/canal/status/tipo toggles
- [X] T006 [P] [US1] Make the same addition to the equivalent `efetividade` change handler in `includes/js/sucesso.js` (~lines 339-354)
- [X] T007 [P] [US1] In `includes/ajax/interacoes/create.php`, change `$profissionais = $_POST['profissionais'];` to `$profissionais = $_POST['profissionais'] ?? [];`
- [X] T008 [P] [US1] In `includes/ajax/interacoes/update.php`, change `$profissionais = $_POST['profissionais'];` to `$profissionais = $_POST['profissionais'] ?? [];`
- [X] T009 [US1] In `includes/js/interacoes.js`'s timeline renderer (~line 183: `.text(interacao['titulo'] + " - " + interacao['profissional'] + " (" + interacao['cargo'] + ")")`), guard against `interacao['profissional']`/`['cargo']` being `null` (e.g. omit the ` - {profissional} ({cargo})` suffix, or substitute a neutral label like "Sem profissional", when null) so a Profissional-less interaction doesn't render the literal text "null". The backend query in `includes/ajax/interacoes/read.php` already `LEFT JOIN`s `Profissional新`/`Cargo新`, so the interaction row itself is confirmed to still appear — only this display string needs the fix
- [ ] T010 [US1] Manually verify User Story 1 per `quickstart.md` § "US1" (depends on T003-T009) — **not run**: requires a human clicking through the actual browser UI, which is outside this session's tools. Code changes lint clean (`php -l` on all 4 touched PHP files, `node --check` on both touched JS files) but that only proves syntax validity, not the actual on-screen behavior.

**Ad-hoc addition (user-requested during review, not originally planned)**: Migrated the `profissionais` select in both forms from the manual `initChoicesSelect()` call to the shared declarative `.choices-select-element` (`data-url`/`data-param="empresa"`) pattern, since `choicesParamProviders.empresa` already covered the dynamic empresa id and both backend endpoints (`profissional/read.php`, `profissional/list.php` — the two duplicate forms use different endpoints, preserved as-is) already return the `id`/`nome` shape the default formatter expects. Confirmed `choiceslist.profissionais.setChoiceByValue()` (used on edit-populate) still resolves correctly either way.

**Checkpoint**: User Story 1 is fully functional and independently verifiable — this is the suggested MVP cut. **User-approved 2026-08-21.**

---

## Phase 4: User Story 2 - See a clear empty state instead of an endless spinner (Priority: P2)

**Goal**: A list/table whose query returns zero rows clears its loading indicator and shows a "no results" message, instead of spinning forever.

**Independent Test**: Filter any record list by a term guaranteed to match nothing — the loading indicator clears and an empty-state message appears.

### Implementation for User Story 2

- [X] T011 [P] [US2] In `includes/js/cadastro.js`'s `showPagina()`, guard the `p.forEach(...)` call (e.g. `p?.forEach(...)`, matching the already-correct guard in `inicio.js`) and render a "no results" row into `.objetos-table > tbody` when `total === 0`
- [X] T012 [P] [US2] Add the same "no results" empty-state row (when `total === 0`) to the already-guarded `showPagina()` in `includes/js/bd.js`
- [X] T013 [P] [US2] Add the same "no results" empty-state row (when `total === 0`) to the already-guarded `showPagina()` in `includes/js/inicio.js`
- [ ] T014 [US2] Manually verify User Story 2 per `quickstart.md` § "US2" — including a forced ajax error (e.g. temporarily break the request) to confirm it still surfaces as an error and is not swallowed as a "no results" state — exercising at least one list backed by each of the three files (depends on T011-T013) — **not run**: needs a human browser session, same limitation as T010. Only the success-path callback was touched in all three files; the separate error callback in each was left untouched, so existing error handling should be structurally unaffected, but this needs an actual click-through to confirm.

**Checkpoint**: User Stories 1 and 2 both work independently. **Implementation complete, pending user review.**

---

## Phase 5: User Story 3 - Find options by typing any part of their name (Priority: P3)

**Goal**: A typed substring matches an option's label anywhere within it, not only at the start, across every system select.

**Independent Test**: Open any searchable select and type a substring from the middle of a known option's label — the option appears in the results.

### Implementation for User Story 3

- [X] T015 [US3] ~~Raise `fuseOptions.threshold`~~ — **revised during review**: `ignoreLocation: true` was already present before this feature and already removes Fuse's position penalty, so a same-accent, same-case substring match anywhere in a label was never actually blocked by position. Raising `threshold` only worked around a *different*, narrower gap (accented characters typed unaccented, e.g. "Joao" vs "João") by loosening error tolerance generally — which also admits real fuzzy/typo false positives, cutting against "as accurate as possible." Reverted `threshold` to `0.0` in `includes/js/choices-select.js`; no code change was actually needed for FR-007/FR-008 as written (position-independence), since it was already satisfied.
- [ ] T016 [US3] Manually verify User Story 3 per `quickstart.md` § "US3" — confirm a same-accent substring match anywhere in a label already works as-is (depends on T015) — **not run**: same browser limitation as T010/T014.

**Known gap, deliberately deferred (not a bug in this pass)**: accent-insensitive matching (typing "Joao" to find "João") does not work and was not implemented — fixing it precisely would mean bypassing Fuse's fuzzy scoring for an accent-normalized literal substring check (mirroring `cadastro.js`'s `searchObjetos()` convention), which is a larger change to shared infrastructure than this backlog bundle called for. Left as an explicit open question for a product decision, not silently dropped.

**Checkpoint**: User Stories 1 and 2 work independently; User Story 3 required no code change beyond the revert above. **Implementation complete, pending user review.**

---

## Phase 6: User Story 4 - Record "não sei" and multiple children on a Profissional (Priority: P4)

**Goal**: "Não sei" is selectable on personal-data radio groups that today force a definite answer; a Profissional can have any number of children, each with their own name/sex/birth year, replacing the current single fixed child slot.

**Independent Test**: Select "Não sei" on a personal-data question and save; separately, enter a number of children greater than one, fill each child's details, save, and reopen the record to confirm all children were kept.

### Implementation for User Story 4

- [X] T017 [US4] Add a "Não sei" radio option to the `sexo`, `estado_civil`, and `conjuge_sexo` groups in `pages/profissionais/profissionais.php`
- [X] T018 [US4] Replace the single fixed "Possui filho" child block (`campos-filho`) with a "número de filhos" input plus a repeatable per-child fieldset template (nome, sexo incl. "Não sei", nascimento_ano) in `pages/profissionais/profissionais.php`, following the existing `#formacoes-container`/`#enderecos-anteriores-container` add/remove pattern (depends on T017). Implemented as a pure count-driven sync (no individual per-child remove button) rather than an "add one more" click, matching FR-010's literal framing; also removed the now-orphaned `TogglePossuiFilho` JS module and added `.filho-extra` cleanup to `clearExtraFields()`.
- [X] T019 [US4] Implement the dynamic child-fieldset add/remove/renumber logic driven by "número de filhos" in `includes/js/profissionais.js` (new `FilhosDinamicos` module), mirroring the existing formação/endereço-anterior handlers and reusing `GenerateDate.initYearOnly()` per child (depends on T018). Each child's fields are named `filhos[i][nome]`/`filhos[i][sexo]`/`filhos[i][nascimento_ano]` — deliberately not the bare-repeated-name convention formação uses, since that would merge every child's sexo radios into one shared HTML radio group across children.
- [X] T020 [P] [US4] Create the new `ProfissionalFilho新` table (`id`, `profissional`, `nome`, `sexo`, `nascimento_ano`, plus an `idx_profissional` index) via `migrations/create_profissional_filho_table.php`, per `data-model.md`. **Applied to the dev DB** (confirmed live via `SHOW CREATE TABLE`).
- [X] T021 [US4] `migrations/backfill_profissional_filho.php` copies each Profissional's existing `filho_sexo`/`filho_nome`/`filho_nascimento_ano` (where `filho = 1`) into one `ProfissionalFilho新` row; idempotent (skips profissionais that already have a row) (depends on T020). **Ran against the dev DB** — 0 rows migrated (no existing profissional in this dev dataset currently has `filho = 1`).
- [X] T022 [P] [US4] In `includes/ajax/profissional/create.php`, replaced the single `filho_*` insert with one `ProfissionalFilho新` insert per submitted child; removed `filho`/`filho_sexo`/`filho_nome`/`filho_nascimento_ano` from the `Profissional新` INSERT (33→29 columns, bind_param type string corrected and verified by exact count, not eyeballed) (depends on T020)
- [X] T023 [P] [US4] In `includes/ajax/profissional/update.php`, delete existing `ProfissionalFilho新` rows for the profissional and reinsert the submitted set (wipe-and-reinsert, per `data-model.md`); same column/bind_param correction on the `Profissional新` UPDATE (34→30 placeholders, verified by exact count) (depends on T020). Also fixed this file's own **local, stripped-down duplicate** of `getCargoInfoList()`/`qualificacaoCargosDiario()` (a second, mostly-inert copy of the functions canonically defined in `qualificacao.php`) — its `$newData` array referenced the now-removed `$filho`/`$sexo_filho`/`$nome_filho`/`$ano_nascimento_filho` variables and would have fataled; replaced with one `"filhos" => bool` key.
- [X] T024 [P] [US4] In `includes/ajax/profissional/read.php`, replaced the flat `filho`/`filho_sexo`/`filho_nome`/`filho_nascimento_ano` fields (removed from both the `id`-branch and `cargo`-branch SELECTs) with a `filhos` array populated from `ProfissionalFilho新`, following the same per-field follow-up-query pattern already used for emails/telefones/hobbies (depends on T020)
- [X] T025 [P] [US4] In `includes/ajax/indicadores/qualificacao.php`'s `getDadosCargos()` and `getCargoInfoList()`: (a) treat the literal string value `"nao_sei"` as unfilled/empty in the qualification-completeness ratio via a custom `array_filter` callback, and (b) **expanded scope**: also removed the same 4 stale `filho_*` columns from both functions' SELECTs (they'd otherwise silently shrink every profissional's qualification denominator once the form stopped writing them) and replaced with one computed `"filhos" => bool` signal sourced from `ProfissionalFilho新`, consistent with T023's local duplicate. Verified the new SQL (including a nested subquery in `getCargoInfoList()`) executes cleanly against the dev DB.
- [ ] T026 [US4] Manually verify User Story 4 per `quickstart.md` § "US4", including the reduce-children-on-edit destructive-edit case and the qualification-index check (depends on T017-T025) — **not run**: same browser limitation as prior stories. All 8 touched files pass `php -l`/`node --check`; the new table, its indexes, the INSERT/SELECT/COUNT SQL shapes, and a throwaway insert-then-delete were all smoke-tested directly against the dev DB and behaved as expected — but the actual form flow (dynamic fieldsets rendering, submission, edit-repopulation) has not been click-tested in a browser.

**Checkpoint**: User Stories 1-4 all work independently. **Implementation complete, pending user review.**

---

## Phase 7: User Story 5 - Show the cellphone number first (Priority: P5)

**Goal**: Wherever a Profissional's phone numbers are shown or edited, cellphone numbers appear before landline numbers.

**Independent Test**: Add a landline number first and a cellphone number second to a Profissional — the cellphone appears first everywhere phone numbers are listed.

### Implementation for User Story 5

- [X] ~~T027 [P] [US5] Add `tipo_linha` column to `Telefone新`~~ — **superseded, reverted**: built and applied to the dev DB first, then dropped again once T028 below found a materially better approach. `Telefone新` ends up with no schema change at all.
- [X] ~~T028 [P] [US5] PHP classification helper in `includes/funcoes/telefone.php`~~ — **superseded, reverted during user review**: `includes/js/profissionais.js` (also `empresa.js`/`geral.js`/`home.js`) already reuses Google's `libphonenumber` (via `getPhoneFormat()`) to pick a mobile-vs-fixed display mask by matching the stored number's digit count against each country's real example numbers — an accurate, already-proven, country-agnostic classification that was just never exposed as one. Replaced the PHP heuristic (Brazil-only, ~19% of dev rows unclassified) with this client-side reuse instead: new `isTelefoneCelular(cc, value)` and `ordenarTelefonesCelularPrimeiro(telefones)` in `profissionais.js`, right after `formatPhone()`. The PHP helper file, its `ajaxheader.php` include, and both migration scripts were deleted; the `tipo_linha` column was dropped from the dev DB.
- [X] T029 [US5] Confirmed `includes/ajax/profissional/update.php` already deletes-and-reinserts `Telefone新` on every edit — **no longer relevant** once T027/T028 were reverted: the final approach never writes to `Telefone新`, so this Principle X instance isn't touched by this feature after all (unflagged in `plan.md` accordingly).
- [X] ~~T030 [US5] Backfill script for `tipo_linha`~~ — **superseded, reverted** along with T027/T028.
- [X] T031 [P] [US5] ~~Persist `tipo_linha` in create.php~~ — **reverted**: `includes/ajax/profissional/create.php`'s phone-insert block is back to its original form, untouched by this feature.
- [X] T032 [P] [US5] ~~Persist `tipo_linha` in update.php~~ — **reverted**: same, `includes/ajax/profissional/update.php`'s phone-insert block is back to its original form.
- [X] T033 [P] [US5] **Replaced its own original approach**: instead of changing `includes/ajax/profissional/read.php`'s SQL (reverted — both phone queries are back to plain `ORDER BY t.id`), applied `ordenarTelefonesCelularPrimeiro()` client-side at the three places `profissionais.js` actually renders a Profissional's phones: the list column (`loadProfissionais`'s row builder), the info panel (`Object.entries(info)` display loop), and the edit-form repopulation on reopen (`fetchProfissionalEdit`, reordering `telefoneddi`/`telefone`/`telefonetipo` together by the same permutation before the rest of that flow consumes them). Verified the classification+sort logic in isolation (4 cases: cellphone-after-landline sorts first, same-type numbers keep relative order for both cellphones and landlines, non-Brazilian numbers with no mobile/fixed distinction pass through unreordered) since the real `libphonenumber` global isn't available outside a browser to test end-to-end here.
- [ ] T034 [US5] Manually verify User Story 5 per `quickstart.md` § "US5" (depends on T027-T033) — **not run**: same browser limitation as prior stories. The classification/sort logic is confirmed correct in isolation; what's unverified is the actual rendering with the real `libphonenumber` global and real DOM across all three sites.

**Checkpoint**: All 5 user stories are independently functional. **Implementation complete, pending user review.**

---

## Phase 8: Polish & Cross-Cutting Concerns

- [X] T035 Full regression sweep of the final diff (`git diff` against the pre-feature baseline, all 23 changed files reviewed line-by-line) — no browser available, so this is a static/DB-level sweep, not the literal `quickstart.md` click-through, which remains a separate outstanding action for a human. Found and fixed nothing new; found and **noted without fixing** one pre-existing, currently-dead issue: `pages/indicadores/indicadores.php` has its own third copy of `getDadosCargos()`/`getCargoInfoList()`/`qualificacaoCargosDiario()` (mirroring the ones already fixed in `qualificacao.php` and `profissional/update.php`) that still reads the stale `filho_*` columns — confirmed inert, since its only two call sites in that file are commented out. Confirmed via direct queries: `ProfissionalFilho新` exists, `Telefone新` has no `tipo_linha` (fully reverted), both as expected. Re-linted all 19 touched `sistema/new` files in one pass — all clean.
- [X] T036 [P] Re-checked the final diff against `plan.md`'s Constitution Check table, principle by principle — all still PASS, none newly violated. The `Telefone新` wipe-and-reinsert flag from the intermediate US5 design no longer applies (that design was reverted); the `ProfissionalFilho新` wipe-and-reinsert flag (US4) still stands, unblocked, per the constitution's own carve-out for child-row wipe-and-reinsert.

---

## Dependencies & Execution Order

### Phase Dependencies

- **Setup (Phase 1)**: No dependencies — start immediately.
- **Foundational (Phase 2)**: Empty — nothing blocks any user story.
- **User Stories (Phase 3-7)**: Each depends only on Setup, not on each other. They can proceed in any order or fully in parallel; priority order (P1→P5) is a suggestion for incremental delivery, not a hard requirement.
- **Polish (Phase 8)**: Depends on whichever user stories were completed.

### User Story Dependencies

- **US1, US2, US3**: No dependencies on any other story or on new schema — pure edits to existing files.
- **US4**: Self-contained; introduces its own `ProfissionalFilho新` table (T020) that only US4 tasks depend on.
- **US5**: Self-contained; introduces its own `Telefone新.tipo_linha` column (T027) that only US5 tasks depend on.

### Within Each User Story

- US1: T003-T008 are independent file edits; T009 (display guard) can proceed alongside them (different file, no shared dependency) → T010 verification depends on all of T003-T009.
- US2: T011-T013 are independent per-file fixes (same bug pattern, three files) → T014 verification.
- US3: T015 (single config change) → T016 verification.
- US4: T017 → T018 → T019 (same file chain, profissionais.php then profissionais.js) run alongside T020 → T021 and T020 → {T022, T023, T024} (schema then backend), plus independent T025 → all converge at T026 verification.
- US5: T027/T028/T029/T030/T031/T032 all ended up superseded/reverted or no longer relevant (see their notes above) — the story collapsed to a single real task, T033 (client-side classify+sort in `profissionais.js`, no dependencies), → T034 verification.

### Parallel Opportunities

- All Setup tasks marked [P] can run together.
- Within US1: T003-T009 (7 tasks, 7 different files) can all run in parallel.
- Within US2: T011, T012, T013 (3 different files) can all run in parallel.
- Within US4: T020 can run in parallel with T017-T019; once T020 lands, T022, T023, T024, T025 can all run in parallel.
- Within US5: N/A — the story is a single task (T033) after the schema/helper approach was reverted.
- Different user stories can be worked on fully in parallel by different people, since none share files or schema.

---

## Parallel Example: User Story 1

```bash
# All of US1's implementation tasks touch different files and can run together:
Task: "Remove required attribute in pages/interacoes/interacoes.php"
Task: "Remove required attribute in pages/interacoes/sucesso.php"
Task: "Add efetividade toggle in includes/js/interacoes.js"
Task: "Add efetividade toggle in includes/js/sucesso.js"
Task: "Add ?? [] default in includes/ajax/interacoes/create.php"
Task: "Add ?? [] default in includes/ajax/interacoes/update.php"
Task: "Guard null profissional/cargo in interacoes.js timeline renderer"
```

## Parallel Example: User Story 4 (post-schema)

```bash
# Once T020 (ProfissionalFilho新 table) lands, these run together:
Task: "Backfill legacy filho_* data into ProfissionalFilho新 in migrations/"
Task: "Insert per-child ProfissionalFilho新 rows in includes/ajax/profissional/create.php"
Task: "Wipe-and-reinsert ProfissionalFilho新 rows in includes/ajax/profissional/update.php"
Task: "Return filhos[] array in includes/ajax/profissional/read.php"
Task: "Treat nao_sei as unfilled in includes/ajax/indicadores/qualificacao.php"
```

---

## Implementation Strategy

### MVP First (User Story 1 Only)

1. Complete Phase 1: Setup.
2. Skip Phase 2 — nothing there.
3. Complete Phase 3: User Story 1 (T003-T010).
4. **STOP and VALIDATE**: run `quickstart.md` § "US1" independently.
5. Ship — this alone fixes the data-loss bug of unloggable non-effective interactions.

### Incremental Delivery

1. Setup → ready to work.
2. US1 (P1) → verify → ship (MVP).
3. US2 (P2) → verify → ship.
4. US3 (P3) → verify → ship.
5. US4 (P4) → verify → ship.
6. US5 (P5) → verify → ship.
7. Each story adds value independently; none block or depend on another, so this order can be reshuffled freely if priorities change mid-stream.

### Parallel Team Strategy

With multiple people available:

- Everyone skips the empty Foundational phase.
- Person A: US1 (small, fast, highest priority — do this regardless of team size).
- Person B: US2 (small, independent).
- Person C: US3 (single-line config change, independent).
- Person D: US4 (largest story — schema + backend + frontend).
- Person E: US5 (client-side only, smallest of the five).
- All 5 converge independently; Polish (Phase 8) runs once all desired stories are in.

---

## Notes

- [P] tasks touch different files with no dependency on an incomplete task.
- [Story] labels map every user-story-phase task back to `spec.md`'s US1-US5 for traceability.
- No automated tests exist or were requested; each story's last task is a manual pass against `quickstart.md`, consistent with Constitution Principle VIII.
- Every file touched already exists, except two new migration scripts for US4's schema change (T020, T021) — no new pages, ajax modules, or forked files anywhere in this task list (Constitution Principles IV/V). US5 originally added a third piece of new infrastructure (a column, two migrations, a PHP helper) but that was fully reverted in favor of a client-side-only approach once a better existing pattern was found — see T027-T028's notes above and `research.md` §5.
