# N4STi Gestão

ERP modular multiempresa em PHP 8.3+ sem framework (monólito modular,
Bootstrap 5 + jQuery locais, MySQL 8). A especificação completa está em
[`openspec/`](openspec/00_README.md) — leia `openspec/CLAUDE.md` antes de
mexer na arquitetura.

Fundação técnica (Marco 0) completa: autenticação, contexto multiempresa,
RBAC, auditoria, módulos habilitáveis por empresa (com bloqueio real no
backend, não só no menu) e o shell do Dashboard. E-mail rastreável (menu
"E-mails" — pendente/entregue/falhou, com reenvio; SMTP real ainda pendente
da senha de `gestao@n4sti.com.br`).

Módulos de negócio em produção, todos com módulo habilitável por empresa,
RBAC, auditoria e teste de ponta a ponta no servidor:

- **Empresas** (com upload de logo) e **Usuários** — sempre ativos.
- **Clientes e Fornecedores** (`/clientes-fornecedores`) — cliente/fornecedor/
  transportadora/industrializador/representante/funcionário no mesmo
  cadastro, com endereços e telefones múltiplos. Sempre ativo (Vendas/
  Compras dependem dele).
- **Produtos** (`/produtos`) — unidades, SKU, preço, imagens.
- **Orçamento** (`/orcamentos`) — versionado: rascunho edita no lugar,
  depois de enviado toda edição gera versão nova sem perder a anterior.
- **Pedido de Venda** (`/pedidos`) — direto ou convertido de orçamento
  aprovado; confirmar reserva estoque de verdade (bloqueia se não houver
  saldo disponível); confirmado não se edita, só cancela com motivo.
- **Estoque** (`/estoque`) — ledger de movimentos imutável (nunca um
  `UPDATE` de saldo fora do serviço), saldo por depósito/status somado dos
  movimentos, idempotente (reenvio não duplica), entrada manual (ainda não
  há Compras/NF de Entrada para alimentar estoque de outro jeito).

Estoque real (baixa definitiva na expedição), Financeiro, Produção, NF-e e
Mercado Livre entram nos próximos
marcos, um de cada vez.

## Stack

- PHP 8.3+ (sem framework), Composer só para autoload PSR-4.
- MySQL 8, `utf8mb4`, InnoDB, SQL mode estrito.
- Apache 2.4 + PHP-FPM.
- Bootstrap 5, Bootstrap Icons e jQuery — arquivos locais em
  `public/assets/vendor/bs/`, nunca CDN.
- PHPUnit para testes.

## Ambiente de desenvolvimento local

1. Instale PHP 8.3+, [Composer](https://getcomposer.org/) e MySQL 8 (no
   Windows, o caminho mais simples é [Laragon](https://laragon.org/) ou
   [XAMPP](https://www.apachefriends.org/)).
2. `composer install`
3. `copy .env.example .env` e preencha `DB_HOST`/`DB_DATABASE`/`DB_USERNAME`/`DB_PASSWORD`.
4. Crie o banco (`CREATE DATABASE n4sti_gestao CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;`).
5. `php bin/console migrate`
6. `php bin/console seed` — cria o Grupo Demo, uma empresa, um administrador
   e imprime a senha inicial no terminal (guarde-a, não fica salva em
   lugar nenhum).
7. `php -S localhost:8080 -t public` e acesse `http://localhost:8080/login`.
8. `vendor/bin/phpunit` — os testes em `tests/Unit` rodam sem banco; os de
   `tests/Integration` pulam automaticamente se `DB_*` não apontar para um
   MySQL alcançável.

## Servidor (produção)

Provisionado em `gestao.n4sti.com.br` (Ubuntu 24.04, IP `191.96.79.149`,
SSH na porta 2222):

- Apache 2.4 + PHP 8.3-FPM + MySQL 8 instalados e ativos.
- Certificado Let's Encrypt emitido via `acme.sh` (`/root/.acme.sh`), instalado
  em `/etc/ssl/gestao.n4sti.com.br.{crt,key}` e `/etc/ssl/fullchain.pem`,
  com `--reloadcmd "systemctl reload apache2"` e renovação diária às 03:15
  no crontab do root.
- vhosts em `/etc/apache2/sites-available/gestao.n4sti.com.br{,-ssl}.conf`;
  porta 80 redireciona para 443 (exceto `/.well-known/acme-challenge/`,
  usado pelas renovações).
- `DocumentRoot` do site é `/var/www/html/public` — o deploy publica o
  projeto inteiro em `/var/www/html` (padrão Ubuntu; ver `.vscode/sftp.json`,
  não versionado) e só `public/` fica exposto, como a spec exige. A pasta
  `.well-known/acme-challenge/` usada pelo `acme.sh` convive no mesmo
  `/var/www/html`, fora de `public/`.
- Banco `n4sti_gestao` e usuário de mesmo nome já criados no MySQL; a senha
  do MySQL (`root` e do usuário da aplicação) é a mesma já usada nos outros
  projetos N4STi.
- **Já publicado e funcionando**: Composer instalado, `composer install`,
  `.env` de produção, `migrate` e `seed` já rodados nesta sessão — login,
  dashboard, cadastro de empresas/usuários e auditoria testados de ponta a
  ponta em `https://gestao.n4sti.com.br`. Para reimplantar depois de mudar
  código: subir os arquivos em `/var/www/html` e, se mudou dependência,
  rodar `composer install --no-dev -o` de novo; migrations novas exigem
  `php bin/console migrate`.

## Comandos úteis

```
php bin/console migrate         # aplica migrations pendentes
php bin/console migrate:rollback# desfaz a última migration
php bin/console migrate:status  # lista aplicadas/pendentes
php bin/console seed             # seed mínimo demonstrativo (idempotente)
php bin/console permissions:sync # sincroniza permissões/papéis novos sem tocar nos dados de demo
```

## Estrutura

```
app/Core/        núcleo transversal (Http, Database, Auth, Authorization,
                 Tenancy, Security, View, Logging, Validation, Events)
app/Modules/     Identity, Organizations, Platform, People, Catalog, Sales,
                 Inventory (mais módulos por marco)
database/        migrations próprias (sem ORM) e seed
public/          único diretório exposto pelo Apache
routes/          web.php (HTML) e api.php (/api/v1)
tests/           Unit (sem banco) e Integration (precisa de MySQL)
openspec/        especificação mestra — leia antes de mudar arquitetura
```

## Limitações conhecidas desta entrega

- **Nenhum e-mail sai de verdade ainda.** `MAIL_HOST` está vazio no
  `.env` — aguardando a senha de `gestao@n4sti.com.br` para configurar o
  SMTP. Enquanto isso, todo envio (recuperação de senha, boas-vindas de
  usuário novo) passa por `App\Modules\Platform\Application\EmailService`,
  fica registrado em `email_messages` (menu "E-mails", com status e
  reenvio) e o conteúdo cai no log estruturado via `LogMailer`. Assim que
  a senha chegar, trocar `App\Core\Mail\MailerFactory::make()` para devolver
  um `SmtpMailer` (PHPMailer) é a única mudança necessária — nada mais no
  app precisa saber que o transporte mudou.
- Orçamento, Pedido e NF-e ainda não existem, então o envio desses
  documentos para cliente/contador (pedido pelo usuário) só entra quando
  esses módulos forem construídos — usando o mesmo `EmailService`.
- Papéis (roles) só podem ser atribuídos por grupo inteiro nesta tela de
  Usuários (o banco já suporta papel restrito a uma empresa via
  `membership_roles.company_id`; a UI para isso fica para quando um módulo
  realmente precisar da distinção).
- CNPJ é validado por tamanho (14 dígitos), não por dígito verificador.
- Testes executados: `vendor/bin/phpunit` rodou as 15 suítes (unitárias +
  integração) contra o MySQL real do servidor, todas passando — inclusive
  o teste de isolamento entre grupos (Gate B) e o de revogação vencendo
  concessão de papel.
