# Implementation Plan: General CRM Backlog Fixes

**Branch**: `fix/general-backlog` | **Date**: 2026-08-21 | **Spec**: [spec.md](./spec.md)

**Input**: Feature specification from `/specs/002-general-backlog-fixes/spec.md`

## Summary

Five independent, low-risk fixes/enhancements to the existing `sistema/new` CRM, bundled together as one backlog sweep: (1) let non-effective Interações save without a Profissional by relaxing a hardcoded frontend `required` attribute in two duplicate interação forms, plus guarding the interaction-history display against a missing Profissional; (2) add a "Não sei" radio option plus a real number-of-children flow (backed by a new one-to-many child table, replacing the current single fixed-column child) to the Profissional form; (3) fix an over-strict Fuse.js search configuration in the shared `choices-select.js` helper so system-wide searchable selects match a term anywhere in a label; (4) patch a missing null-guard in one of three near-duplicate `showPagina()` implementations so empty result sets clear the loading state instead of throwing; (5) reorder cellphones before landlines wherever a Profissional's phone data is shown, entirely client-side, reusing the country-aware `libphonenumber` digit-count match the codebase already used for phone-mask formatting — no schema change. No new services, no new pages, no new ajax/page modules, no new external dependencies — every change lands inside the existing pages/ajax/js triad, plus a small number of new one-off files consistent with existing conventions (two migration scripts for US4's schema change, and nothing new for US5 — its first pass added a column/helper/two migrations that were fully reverted once a better client-side approach was found; see `research.md` §5).

## Technical Context

**Language/Version**: PHP 8.0 (server), vanilla JavaScript + jQuery (client, no bundler/transpiler)

**Primary Dependencies**: mysqli (DB access, prepared statements only), Choices.js + its bundled Fuse.js (searchable selects, via `includes/js/choices-select.js`), Google `libphonenumber` JS build (already vendored client-side for input masking in `includes/js/empresa.js`, informs but is not directly reused for the new server-side classification — see research.md)

**Storage**: MySQL, accessed only via mysqli prepared statements; no ORM, no migration framework (`sistema/new/migrations/` is one-off scripts, not schema-versioned)

**Testing**: No automated test suite exists in this codebase (Constitution Principle VIII). Manual end-to-end verification against the flows in `quickstart.md` is the actual bar for "done."

**Target Platform**: Apache + PHP 8 under Docker Compose (server); any evergreen browser rendering server-rendered PHP pages with jQuery (client)

**Project Type**: Single monolithic web application — `sistema/new`, structured as pages/ajax/js triads (Constitution Principle V). No frontend/backend repo split.

**Performance Goals**: None newly introduced; changes must not make existing list/select/form interactions perceptibly slower than today.

**Constraints**: Tenant isolation via `grupo` filtering (Principle I), parameterized queries only (Principle II), auth gate on every touched/new ajax entry point (Principle III), no new sibling/fork files (Principle IV), all new functionality lands inside the existing triad structure (Principle V), no secrets in source (Principle VI), production errors fail safe, not loud (Principle VII), no hard deletes of main data objects (Principle X) — see Constitution Check below for how each item in this plan is checked against these.

**Scale/Scope**: Internal CRM, tens of concurrent internal sales/ops users, single MySQL instance partitioned by `grupo`. Touches roughly a dozen existing files across 5 independent user stories; no new pages or ajax modules.

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

| Principle | Check | Status |
|---|---|---|
| I. Tenant isolation (`grupo`) | New queries (Filho child table CRUD) reach `Profissional新` rows only through IDs already resolved from a `grupo`-scoped Profissional/Empresa lookup upstream, matching the existing `Telefone新` access pattern (no direct `grupo` column on child rows, tenant-checked via parent chain). No new query bypasses this chain. US5 touches no SQL at all — its phone-ordering logic is entirely client-side. | PASS |
| II. Parameterized queries only | All new/modified SQL (Filho table CRUD) uses mysqli `prepare()`/`bind_param()`, matching every file touched. | PASS |
| III. Auth gate on every entry point | All touched ajax files already start with `include '../../parts/ajaxheader.php'`; no new ajax file is being added outside that pattern. | PASS |
| IV. No new forks | No `-dev`/`-old`/`-bak`/`v2` files created. `pages/interacoes/sucesso.php` and `pages/interacoes/interacoes.php` are pre-existing duplicate forms (not created by this work) that both need the same fix — this plan touches both in place rather than forking a third copy. Likewise `bd.js`/`inicio.js`/`cadastro.js` already have three near-duplicate `showPagina()` copies; this plan patches the one broken copy to match its two already-correct siblings, it does not add a fourth. | PASS |
| V. Pages/Ajax/JS triad | Every touched file already lives inside `pages/<feature>/`, `includes/ajax/<feature>/`, or `includes/js/<feature>.js`. No legacy root-level monolith file (e.g. the frozen `add_interacao*.php` variants) is extended. | PASS |
| VI. No secrets in source | Not applicable — no secrets involved in this work. | PASS |
| VII. Errors fail safe | The loading-spinner fix (US2) is specifically about replacing an uncaught client-side exception with a guarded, safe empty-state render — directly in service of this principle. | PASS |
| VIII. Manual verification standard | `quickstart.md` (Phase 1) is the manual verification guide for all 5 user stories, since no automated suite exists. | PASS |
| IX. Plataforma API auth | Not applicable — unrelated subsystem. | N/A |
| X. No hard deletes for main objects | Reworking "número de filhos" (US4) replaces a Profissional's child rows on edit (wipe current children, reinsert the new set) — Filho is a related/child row of Profissional, not itself a listed main object (`Usuarios`, `Empresa新`, `Contato`, `Contrato`), and wipe-and-reinsert of related rows is a constitution-recognized pattern. **Flagged per the constitution's own instruction that every such instance is a warn-and-discuss case, not an automatic pass** — noted here for reviewer visibility rather than silently done. (An earlier pass also flagged `Telefone新`'s existing wipe-and-reinsert in `update.php`, from when US5 wrote a `tipo_linha` column through it — no longer applicable, since US5's final client-side approach doesn't write to `Telefone新` at all.) | PASS (flagged, not blocked) |

No unjustified violations. Complexity Tracking table below is empty as a result.

**Post-Phase-1 re-check**: `data-model.md` (new `ProfissionalFilho新` table) and `contracts/` were reviewed against the same table above after design — nothing new surfaces. The new child table follows the existing `Telefone新` transitive-tenant-isolation pattern (Principle I), every new query is parameterized (Principle II), no new ajax file or fork was introduced (Principles III/IV), and the wipe-and-reinsert of `ProfissionalFilho新` remains the one flagged (not blocked) Principle X item noted above. Gate still PASSes.

**Post-implementation note on US5**: the design/research phase originally called for a `Telefone新.tipo_linha` column, two migration scripts, and a new `includes/funcoes/telefone.php` helper (all reflected in this Constitution Check's earlier revisions). During implementation, a materially better pattern was found already living in `includes/js/profissionais.js` — see `research.md` §5 — and the schema/migration/helper approach was fully reverted in favor of a client-side-only one. `Telefone新` and its write path end up completely untouched by this feature; the gate re-check above reflects that final state, not the intermediate one.

## Project Structure

### Documentation (this feature)

```text
specs/002-general-backlog-fixes/
├── plan.md              # This file (/speckit-plan command output)
├── research.md          # Phase 0 output (/speckit-plan command)
├── data-model.md         # Phase 1 output (/speckit-plan command)
├── quickstart.md        # Phase 1 output (/speckit-plan command)
├── contracts/           # Phase 1 output (/speckit-plan command)
│   ├── interacoes-create-update.md
│   └── profissional-create-update-read.md
└── tasks.md             # Phase 2 output (/speckit-tasks command - NOT created by /speckit-plan)
```

### Source Code (repository root)

This feature modifies existing pages/ajax/js triads and shared JS helper files, plus a small number of new one-off files consistent with existing directory conventions (new migration scripts under `migrations/`, one new helper alongside its existing siblings under `includes/funcoes/`) — no new pages or ajax modules, all under `sistema/new/`:

```text
sistema/new/
├── pages/
│   └── interacoes/
│       ├── interacoes.php         # US1: relax `required` on Profissionais select
│       └── sucesso.php            # US1: same relaxation, duplicate form
│   └── profissionais/
│       └── profissionais.php      # US4: "Não sei" radio options + número de filhos UI
├── includes/
│   ├── ajax/
│   │   ├── interacoes/
│   │   │   ├── create.php         # US1: confirmed already tolerates 0 Profissionais; add `?? []` default to silence an undefined-index warning
│   │   │   ├── update.php         # US1: same confirmed tolerance (wipe-and-reinsert); same `?? []` hardening
│   │   │   └── read.php           # US1: confirmed LEFT JOINs already keep a Profissional-less interaction in results; no query change needed
│   │   ├── profissional/
│   │   │   ├── create.php         # US4: write N Filho rows; untouched by US5 (reverted — see research.md §5)
│   │   │   ├── update.php         # US4: wipe-and-reinsert Filho rows; untouched by US5 (reverted)
│   │   │   └── read.php           # US4: return Filho array; untouched by US5 (reverted) — still plain `ORDER BY t.id`
│   │   └── indicadores/
│   │       └── qualificacao.php   # US4: treat "nao_sei" as unfilled
│   └── js/
│       ├── interacoes.js          # US1: toggle Profissionais `required` with efetividade; guard timeline display against a null profissional/cargo
│       ├── sucesso.js             # US1: same required/efetividade toggle, duplicate form logic
│       ├── profissionais.js       # US4: dynamic child fieldset rendering; US5: isTelefoneCelular()/ordenarTelefonesCelularPrimeiro() + applied at 3 render sites
│       ├── choices-select.js      # US3: Fuse.js search config (shared by every system select)
│       ├── cadastro.js            # US2: fix missing null-guard in `showPagina()`
│       ├── bd.js                  # US2: reference — already has the correct guard
│       └── inicio.js              # US2: reference — already has the correct guard
└── migrations/
    ├── (new, US4) create ProfissionalFilho新 table
    └── (new, US4) backfill legacy filho_* data into ProfissionalFilho新
```

**Structure Decision**: Single existing project, no new directories. Every change is a modification to a file that already exists inside the established pages/ajax/js triad (or, for the two client-side-only fixes — US2 and US3 — inside an existing shared `includes/js/*.js` helper). This matches Constitution Principle V directly: there is no case here where new functionality needs a new triad scaffolded.

## Complexity Tracking

*No entries — no unjustified Constitution Check violations.*
