Para devs y CTOs
Él escribe el CRUD. Tú escribes el resto.
Tablas, pantallas y reglas armadas en lo visual, en el código o con el agente. Sale Eloquent, Blade y Alpine en un proyecto Laravel estándar, que tú revisas, versionas y mantienes.
Lee lo que genera antes de confiar.
Un formulario y los tres archivos que produce. Pasa el mouse sobre un campo y mira sus líneas en el Blade, en el model y en el controller.
resources/views/venda-form.blade.php
<mad-form submit="onSave" data-mad-entity-id="693"><mad-form-section title="Cadastro de Venda" icon="file-text"><mad-form-grid :cols="2"><mad-dbcombo-field data-mad-column-id="4533" data-mad-entity-id="693" data-mad-ref-entity-id="691" data-mad-ref-column-id="4521" data-mad-ref-display-column-id="4522" name="pessoa_id" label="Pessoa" database="app" model="Pessoa" key="id" display="{nome}" order-by="nome" order="asc" :required="true" /><mad-date-field data-mad-column-id="4534" data-mad-entity-id="693" name="data" label="Data" :required="true" /></mad-form-grid><mad-form-grid :cols="2"><mad-money-field name="total" label="Total" prefix="R$" :decimals="2" :readonly="true" /><mad-input-field data-mad-column-id="4536" data-mad-entity-id="693" name="status" label="Status" /></mad-form-grid></mad-form-section><mad-form-actions><mad-btn variant="primary" type="submit" icon="save">Salvar</mad-btn></mad-form-actions></mad-form> app/Models/App/Venda.php
<?phpnamespace App\Models\App;use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsTo;use Illuminate\Database\Eloquent\Relations\HasMany;use Mad\Database\Concerns\HasIdPolicy;use Mad\Database\Concerns\HasMadAudit;class Venda extends Model{use HasIdPolicy, HasMadAudit;protected $connection = 'app';protected $table = 'venda';// Colunas de auditoria/soft-delete desta tabela (chave ausente = desligada).protected array $madAudit = ['created_at' => 'created_at','updated_at' => 'updated_at',];protected $fillable = ['pessoa_id','data','total','status',];public static function rules($id = null): array{return ['pessoa_id' => 'required|integer','data' => 'required','total' => 'nullable|numeric','status' => 'nullable|max:20',];}public function pessoa(): BelongsTo{return $this->belongsTo(Pessoa::class, 'pessoa_id');}public function vendaItems(): HasMany{return $this->hasMany(VendaItem::class, 'venda_id');}protected static function booted(): void{static::deleting(function (self $model) {if (VendaItem::where('venda_id', '=', $model->id)->exists()) {throw new \Exception('Não é possível deletar este registro pois ele está sendo utilizado em outra parte do sistema');}});}} app/Control/VendaForm.php
<?phpuse Mad\Component\MadComponent;use Mad\Form\MadForm;use Mad\Form\MadValidationException;use Mad\Http\MadResponse;use Mad\Ui\MadMessage;use App\Models\App\Venda;use Illuminate\Support\Facades\DB;// use App\Control\VendaList;// @mad-block:user:uses {// @mad-block:user:uses }/*** @mad-generated* @mad-entity-id 693*/class VendaForm extends MadComponent{// @mad-block:config:properties {protected static string $wrapper = self::DRAWER;protected static string $title = 'Cadastro de Venda';protected static string $size = '600px';protected static string $side = 'right';public MadForm $form;// int|string: tabela cuja PK é varchar/char (código, matrícula) tem chave// de texto — `int` transformaria 'ABC-1' em 0 e salvaria no registro errado.public int|string|null $recordId = null;// @mad-block:config:properties }// @mad-block:user:properties {// @mad-block:user:properties }// @mad-block:lifecycle:mount {public function mount(array $params = []): void{$this->form = new MadForm('form');if (! empty($params['id'])) {$this->onEdit($params['id']);}}// @mad-block:lifecycle:mount }// @mad-block:lifecycle:onEdit {public function onEdit(int|string $id): void{// @mad-block:locked:onEdit:master {$venda = Venda::findOrFail($id);$this->recordId = $venda->getKey();// @mad-block:locked:onEdit:master }// @mad-block:detail-load-hooks {// @mad-block:detail-load-hooks }$this->form->fill($venda);}// @mad-block:lifecycle:onEdit }// @mad-block:lifecycle:onSave {public function onSave(): MadResponse{try {$this->form->validate(Venda::rules($this->recordId));$venda = DB::connection('app')->transaction(function () {// @mad-block:locked:onSave:master {$venda = Venda::findOrNew($this->recordId);// @mad-block:locked:onSave:master }// @mad-block:detail-save-hooks {// @mad-block:detail-save-hooks }$this->form->save($venda);$this->recordId = $venda->getKey();return $venda;});// @mad-block:combo-origin {// Tela aberta por um combo ("Sem resultados → Cadastrar novo"):// devolve a option nova selecionada e fecha o overlay. Fora desse// caso returnToCombo() responde null e o fluxo normal segue.if (($__combo = $this->returnToCombo($this->recordId)) !== null) {return $__combo;}// @mad-block:combo-origin }// @mad-block:after-save {return (new MadResponse())->toast('Registro salvo com sucesso!', 'success')->closeDrawer()->manageRow($this->recordId, VendaList::class);// @mad-block:after-save }} catch (MadValidationException $e) {return $e->asInline();} catch (\Throwable $e) {return MadMessage::error('Erro ao salvar', $e->getMessage());}}// @mad-block:lifecycle:onSave }// Caminho da view gerado pela plataforma a partir da PASTA DO MÓDULO.// Bloco byte-exact: NÃO edite — trocar o módulo regenera este path sozinho.// @mad-block:locked:view-name {protected function viewName(): string{return 'venda-form';}// @mad-block:locked:view-name }// @mad-block:lifecycle:view {protected function view(): string|array{$replaces = [];return [$this->viewName(), $replaces];}// @mad-block:lifecycle:view }// @mad-block:user:functions {// @mad-block:user:functions }} Tu código sobrevive a la próxima generación.
El controller generado se divide en bloques. Los que controla la interfaz quedan bloqueados. Los @mad-block:user son tuyos: cuando la pantalla cambia y el archivo se genera de nuevo, quedan como los dejaste.
- Imports, propiedades y funciones tuyas en bloques propios
- Validación con el
rules()del model, guardado en transacción - Un componente PHP que lees de arriba abajo, sobre el MadComponent del framework
<?phpuse Mad\Component\MadComponent;use Mad\Form\MadForm;use Mad\Form\MadValidationException;use Mad\Http\MadResponse;use Mad\Ui\MadMessage;use App\Models\App\Venda;use Illuminate\Support\Facades\DB;// use App\Control\VendaList;// @mad-block:user:uses {// @mad-block:user:uses }/*** @mad-generated* @mad-entity-id 693*/class VendaForm extends MadComponent{// @mad-block:config:properties {protected static string $wrapper = self::DRAWER;protected static string $title = 'Cadastro de Venda';protected static string $size = '600px';protected static string $side = 'right';public MadForm $form;// int|string: tabela cuja PK é varchar/char (código, matrícula) tem chave// de texto — `int` transformaria 'ABC-1' em 0 e salvaria no registro errado.public int|string|null $recordId = null;// @mad-block:config:properties }// @mad-block:user:properties {// @mad-block:user:properties }// @mad-block:lifecycle:mount {public function mount(array $params = []): void{$this->form = new MadForm('form');if (! empty($params['id'])) {$this->onEdit($params['id']);}}// @mad-block:lifecycle:mount }// @mad-block:lifecycle:onEdit {public function onEdit(int|string $id): void{// @mad-block:locked:onEdit:master {$venda = Venda::findOrFail($id);$this->recordId = $venda->getKey();// @mad-block:locked:onEdit:master }// @mad-block:detail-load-hooks {// @mad-block:detail-load-hooks }$this->form->fill($venda);}// @mad-block:lifecycle:onEdit }// @mad-block:lifecycle:onSave {public function onSave(): MadResponse{try {$this->form->validate(Venda::rules($this->recordId));$venda = DB::connection('app')->transaction(function () {// @mad-block:locked:onSave:master {$venda = Venda::findOrNew($this->recordId);// @mad-block:locked:onSave:master }// @mad-block:detail-save-hooks {// @mad-block:detail-save-hooks }$this->form->save($venda);$this->recordId = $venda->getKey();return $venda;});// @mad-block:combo-origin {// Tela aberta por um combo ("Sem resultados → Cadastrar novo"):// devolve a option nova selecionada e fecha o overlay. Fora desse// caso returnToCombo() responde null e o fluxo normal segue.if (($__combo = $this->returnToCombo($this->recordId)) !== null) {return $__combo;}// @mad-block:combo-origin }// @mad-block:after-save {return (new MadResponse())->toast('Registro salvo com sucesso!', 'success')->closeDrawer()->manageRow($this->recordId, VendaList::class);// @mad-block:after-save }} catch (MadValidationException $e) {return $e->asInline();} catch (\Throwable $e) {return MadMessage::error('Erro ao salvar', $e->getMessage());}}// @mad-block:lifecycle:onSave }// Caminho da view gerado pela plataforma a partir da PASTA DO MÓDULO.// Bloco byte-exact: NÃO edite — trocar o módulo regenera este path sozinho.// @mad-block:locked:view-name {protected function viewName(): string{return 'venda-form';}// @mad-block:locked:view-name }// @mad-block:lifecycle:view {protected function view(): string|array{$replaces = [];return [$this->viewName(), $replaces];}// @mad-block:lifecycle:view }// @mad-block:user:functions {// @mad-block:user:functions }} Trae tu agente. O usa el nuestro.
El agente de MadBuilder lee la base de datos, crea tablas y genera pantallas en el mismo proyecto que tú editas. Escribe la tag del componente MAD, no el HTML, el CSS y el JS que hay detrás, por eso gasta de 65% a 75% menos tokens.
$ claude mcp add madbuilder --token mgw_•••• --project 1042 - Claude Code, Cursor o cualquier cliente MCP
- Token exclusivo por proyecto, con revocación al instante
- Permisos por herramienta y auditoría de cada llamada
- Tu clave de Anthropic, OpenRouter, xAI, Z.AI o Ark, o el Mad Coding Plan
Tú 14:05
Crea un CRUD de venta con ítems (maestro-detalle): la venta tiene cliente, fecha y valor total; cada ítem tiene producto, cantidad y valor unitario. Genera el listado y el formulario.
Agente 14:08
- Voy a crear el CRUD maestro-detalle de venta. Primero necesito revisar la base de datos y crear las tablas que todavía no existen.
mad_db_inspect list_tables17 tablas en el modelo, ninguna de venta- Necesito crear produto, venda y venda_item. Dejaré pessoa como cliente.
mad_db_table create produtonome, preco_venda, unidade, ativo y el model Produtomad_db_table create vendacliente_id, data_venda, valor_total y el model Vendamad_db_table create venda_itemvenda_id, produto_id, quantidade, valor_unitario, valor_total y el model VendaItem- Tablas creadas. Ahora voy a cargar la skill del framework MAD y generar el CRUD maestro-detalle.
skill mad-frameworkskill cargadacrud_generate venda + detalhe venda_item2 pantallas creadas: VendaList y VendaForm
Sin motor propietario de por medio.
Lo que corre en producción es tu proyecto Laravel. MadBuilder queda en tu flujo de trabajo, no en tu servidor.
Laravel, Eloquent, Blade y Alpine
La app generada declara laravel/framework ^13.8 y el framework MAD viene dentro del proyecto.
Seis bases de datos
MySQL o MariaDB, PostgreSQL, SQLite, Oracle, SQL Server y Firebird.
SSH, Git o ZIP
El deploy por SSH guarda los últimos 5 backups. El Git va a GitHub, GitLab o Bitbucket.
Entorno de prueba en un clic
PHP 8.4 u 8.5, con PostgreSQL, MySQL, MariaDB o SQLite, aislado por proyecto.
Errores y APM integrados
Rastreo opt-in, sin contratar Sentry o New Relic aparte.
Rutas limpias
Una URL por pantalla, con unicidad validada y rutas reservadas protegidas.
Preguntas de quien va a mantener el código
¿Y si quiero salir de MadBuilder?
Descarga el ZIP o haz push a tu Git. Es un proyecto Laravel común, con el framework MAD dentro de la carpeta packages. Corre sin MadBuilder y cualquier dev Laravel puede mantenerlo.
¿Qué pasa con el código que escribo a mano?
Queda dentro de los bloques @mad-block:user. Cuando alguien cambia la pantalla y el archivo se genera de nuevo, esos bloques se preservan. Las partes que controla la interfaz quedan bloqueadas y marcadas en el editor.
¿Puedo editar directamente el PHP y el Blade?
Puedes. Visual, PHP y Blade son tres vistas del mismo archivo. Lo que cambias en una aparece en las otras.
¿Se puede versionar en Git?
Sí. El Git Deploy hace push a GitHub, GitLab o Bitbucket, y tú eliges las carpetas que no suben, como vendor y storage.
¿Qué bases de datos se soportan?
MySQL o MariaDB, PostgreSQL, SQLite, Oracle, SQL Server y Firebird.
¿Puedo usar mi propio agente de IA en lugar del de ustedes?
Puedes. El proyecto expone un servidor MCP: Claude Code, Cursor o cualquier cliente MCP crea pantallas, modela datos y publica, con un token por proyecto y cada llamada registrada.
Descarga el .zip antes de agendar una reunión.
Crea la cuenta, arma una pantalla y abre el proyecto en tu editor. Si el código no pasa la revisión de tu equipo, perdiste una tarde.
- Crea la cuentaBeta abierta, sin tarjeta.
- Arma una pantallaEn el editor visual, en código o pidiéndoselo a la IA.
- Descarga el .zipUn proyecto Laravel que corre donde quieras.