@artilingo/artiframe-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/assets/logo.png +0 -0
  2. package/assets/logo.svg +22 -0
  3. package/assets/logo.webp +0 -0
  4. package/assets/logo@0.5x.png +0 -0
  5. package/assets/logo@0.75x.png +0 -0
  6. package/assets/logo@1.5x.png +0 -0
  7. package/assets/logo@2x.png +0 -0
  8. package/assets/logo@3x.png +0 -0
  9. package/assets/logo@4x.png +0 -0
  10. package/bin/artiframe.js +57 -0
  11. package/bin/artiframe.php +45 -0
  12. package/core-stubs/app/ApiControl.php +81 -0
  13. package/core-stubs/app/Database.php +69 -0
  14. package/core-stubs/app/DotEnv.php +63 -0
  15. package/core-stubs/app/R2Manager.php +130 -0
  16. package/core-stubs/app/ViewControl.php +24 -0
  17. package/core-stubs/bin/SystemMethod.php +271 -0
  18. package/core-stubs/bin/ViewMethod.php +354 -0
  19. package/core-stubs/docs/de.html +951 -0
  20. package/core-stubs/docs/en.html +952 -0
  21. package/core-stubs/docs/es.html +947 -0
  22. package/core-stubs/docs/fr.html +850 -0
  23. package/core-stubs/docs/tr.html +951 -0
  24. package/core-stubs/public/assets/css/components/footer.css +0 -0
  25. package/core-stubs/public/assets/css/components/header.css +0 -0
  26. package/core-stubs/public/assets/css/components/mobilenav.css +0 -0
  27. package/core-stubs/public/assets/css/components/sidebar.css +0 -0
  28. package/core-stubs/public/assets/css/components/theme-modal.css +0 -0
  29. package/core-stubs/public/assets/css/root/app.css +61 -0
  30. package/core-stubs/public/assets/js/components/header.js +0 -0
  31. package/core-stubs/public/assets/js/components/mobilenav.js +0 -0
  32. package/core-stubs/public/assets/js/components/sidebar.js +0 -0
  33. package/core-stubs/public/assets/js/components/theme-modal.js +0 -0
  34. package/core-stubs/public/assets/js/root/app.js +30 -0
  35. package/core-stubs/public/includes/footer.php +5 -0
  36. package/core-stubs/public/includes/head.php +26 -0
  37. package/core-stubs/public/includes/header.php +5 -0
  38. package/core-stubs/public/includes/mobilenav.php +5 -0
  39. package/core-stubs/public/includes/sidebar.php +5 -0
  40. package/core-stubs/public/includes/theme-modal.php +5 -0
  41. package/core-stubs/readme/de.md +29 -0
  42. package/core-stubs/readme/en.md +29 -0
  43. package/core-stubs/readme/es.md +29 -0
  44. package/core-stubs/readme/fr.md +29 -0
  45. package/core-stubs/readme/tr.md +29 -0
  46. package/package.json +49 -0
  47. package/scripts/postinstall.js +21 -0
  48. package/src/App.php +266 -0
  49. package/src/Commands/MakeApiCommand.php +71 -0
  50. package/src/Commands/MakeClassCommand.php +88 -0
  51. package/src/Commands/MakeViewCommand.php +95 -0
  52. package/src/Commands/NewProjectCommand.php +633 -0
  53. package/src/Commands/VersionCommand.php +92 -0
  54. package/src/Lang/de.php +53 -0
  55. package/src/Lang/en.php +53 -0
  56. package/src/Lang/es.php +53 -0
  57. package/src/Lang/fr.php +53 -0
  58. package/src/Lang/tr.php +53 -0
  59. package/src/Services/Safeguard.php +48 -0
  60. package/src/Services/Translator.php +52 -0
@@ -0,0 +1,947 @@
1
+ <!DOCTYPE html>
2
+ <html lang="es">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ArtiFrame | Documentación Oficial para Desarrolladores</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --bg: #0d1117;
11
+ --surface: #161b22;
12
+ --surface-2: #1c2128;
13
+ --border: #30363d;
14
+ --text: #e6edf3;
15
+ --muted: #7d8590;
16
+ --green: #009d6c;
17
+ --green-light: #00c88c;
18
+ --green-dim: rgba(0,157,108,0.12);
19
+ --blue: #58a6ff;
20
+ --yellow: #e3b341;
21
+ --red: #f85149;
22
+ --purple: #bc8cff;
23
+ --code-bg: #0d1117;
24
+ --sidebar-w: 280px;
25
+ }
26
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
27
+
28
+ html { scroll-behavior: smooth; }
29
+
30
+ body {
31
+ font-family: 'Inter', sans-serif;
32
+ background: var(--bg);
33
+ color: var(--text);
34
+ line-height: 1.75;
35
+ display: flex;
36
+ }
37
+
38
+ /* ---- SIDEBAR ---- */
39
+ .sidebar {
40
+ width: var(--sidebar-w);
41
+ background: var(--surface);
42
+ height: 100vh;
43
+ position: fixed;
44
+ top: 0; left: 0;
45
+ border-right: 1px solid var(--border);
46
+ overflow-y: auto;
47
+ padding: 28px 0 40px;
48
+ display: flex;
49
+ flex-direction: column;
50
+ }
51
+
52
+ .sb-logo {
53
+ padding: 0 22px 24px;
54
+ border-bottom: 1px solid var(--border);
55
+ margin-bottom: 16px;
56
+ }
57
+ .sb-logo strong {
58
+ font-size: 1.35rem;
59
+ font-weight: 700;
60
+ color: var(--green);
61
+ letter-spacing: -0.3px;
62
+ }
63
+ .sb-logo span {
64
+ display: block;
65
+ font-size: 0.75rem;
66
+ color: var(--muted);
67
+ margin-top: 3px;
68
+ }
69
+
70
+ .sb-cat {
71
+ padding: 16px 22px 6px;
72
+ font-size: 0.68rem;
73
+ text-transform: uppercase;
74
+ letter-spacing: 1.2px;
75
+ color: var(--green);
76
+ font-weight: 700;
77
+ }
78
+ .sidebar nav a {
79
+ display: block;
80
+ padding: 7px 22px;
81
+ font-size: 0.88rem;
82
+ color: var(--muted);
83
+ text-decoration: none;
84
+ border-left: 2px solid transparent;
85
+ transition: all .15s;
86
+ }
87
+ .sidebar nav a:hover,
88
+ .sidebar nav a.active {
89
+ color: var(--text);
90
+ background: var(--green-dim);
91
+ border-left-color: var(--green);
92
+ }
93
+
94
+ /* ---- MAIN ---- */
95
+ .content {
96
+ margin-left: var(--sidebar-w);
97
+ padding: 60px 70px 100px;
98
+ max-width: 1100px;
99
+ width: 100%;
100
+ }
101
+
102
+ /* ---- TYPOGRAPHY ---- */
103
+ h1 { font-size: 2.6rem; font-weight: 700; letter-spacing: -0.5px; margin-bottom: 16px; }
104
+ h1 span { color: var(--green); }
105
+ h2 {
106
+ font-size: 1.7rem; font-weight: 700;
107
+ margin: 72px 0 20px;
108
+ padding-bottom: 14px;
109
+ border-bottom: 1px solid var(--border);
110
+ }
111
+ h2 .tag { font-size: 0.65rem; padding: 3px 8px; border-radius: 4px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px; vertical-align: middle; margin-left: 10px; }
112
+ h3 { font-size: 1.1rem; font-weight: 600; margin: 32px 0 12px; color: var(--green-light); display: flex; align-items: center; gap: 10px; }
113
+ p { color: var(--muted); margin-bottom: 16px; font-size: 0.97rem; }
114
+ strong { color: var(--text); }
115
+ ul, ol { margin: 0 0 16px 22px; color: var(--muted); font-size: 0.97rem; }
116
+ li { margin-bottom: 8px; }
117
+ a { color: var(--green-light); text-decoration: none; }
118
+ a:hover { text-decoration: underline; }
119
+
120
+ /* ---- LEAD ---- */
121
+ .lead { font-size: 1.05rem; color: var(--muted); margin-bottom: 0; max-width: 720px; }
122
+
123
+ /* ---- BADGES ---- */
124
+ .badge { font-size: 0.68rem; padding: 3px 8px; border-radius: 4px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px; white-space: nowrap; }
125
+ .b-view { background: rgba(88,166,255,.12); color: #58a6ff; border: 1px solid rgba(88,166,255,.3); }
126
+ .b-api { background: rgba(188,140,255,.12); color: #bc8cff; border: 1px solid rgba(188,140,255,.3); }
127
+ .b-cli { background: rgba(0,200,140,.12); color: #00c88c; border: 1px solid rgba(0,200,140,.3); }
128
+ .b-core { background: rgba(227,179,65,.12); color: #e3b341; border: 1px solid rgba(227,179,65,.3); }
129
+ .b-sec { background: rgba(248,81,73,.12); color: #f85149; border: 1px solid rgba(248,81,73,.3); }
130
+
131
+ /* ---- CODE ---- */
132
+ pre {
133
+ background: var(--code-bg);
134
+ border: 1px solid var(--border);
135
+ border-radius: 8px;
136
+ padding: 22px 24px;
137
+ overflow-x: auto;
138
+ margin: 18px 0 24px;
139
+ font-size: 0.875rem;
140
+ line-height: 1.6;
141
+ }
142
+ code { font-family: 'JetBrains Mono', 'Consolas', monospace; color: #e6edf3; }
143
+ .ic { /* inline code */
144
+ background: var(--surface-2);
145
+ padding: 2px 7px;
146
+ border-radius: 4px;
147
+ font-family: 'JetBrains Mono', monospace;
148
+ font-size: 0.85em;
149
+ color: var(--yellow);
150
+ border: 1px solid var(--border);
151
+ }
152
+ .kw { color: #ff7b72; } /* keyword / php tag */
153
+ .fn { color: #d2a8ff; } /* function name */
154
+ .st { color: #a5d6ff; } /* string */
155
+ .cm { color: #8b949e; } /* comment */
156
+ .nu { color: #79c0ff; } /* number / bool */
157
+ .var { color: #ffa657; } /* variable */
158
+
159
+ /* ---- CARDS ---- */
160
+ .card {
161
+ background: var(--surface);
162
+ border: 1px solid var(--border);
163
+ border-radius: 10px;
164
+ padding: 28px 32px;
165
+ margin-bottom: 20px;
166
+ }
167
+ .card.highlight { border-left: 3px solid var(--green); }
168
+
169
+ /* ---- ALERTS ---- */
170
+ .alert { padding: 18px 22px; border-radius: 8px; margin: 24px 0; border-left: 3px solid; font-size: 0.95rem; }
171
+ .a-info { background: rgba(88,166,255,.08); border-color: var(--blue); color: #93c5fd; }
172
+ .a-warn { background: rgba(227,179,65,.08); border-color: var(--yellow); color: #fbbf24; }
173
+ .a-danger { background: rgba(248,81,73,.08); border-color: var(--red); color: #fca5a5; }
174
+ .a-success { background: rgba(0,157,108,.08); border-color: var(--green); color: #6ee7b7; }
175
+
176
+ /* ---- RETURN TYPE ---- */
177
+ .ret { display: inline-block; font-size: 0.78rem; color: var(--green); font-family: 'JetBrains Mono', monospace; background: rgba(0,157,108,.12); padding: 3px 10px; border-radius: 4px; border: 1px solid rgba(0,157,108,.25); margin-bottom: 12px; }
178
+
179
+ /* ---- GRID ---- */
180
+ .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-bottom: 24px; }
181
+ @media(max-width: 900px) { .grid-2 { grid-template-columns: 1fr; } }
182
+
183
+ /* ---- DIR TREE ---- */
184
+ .tree { background: var(--code-bg); border: 1px solid var(--border); border-radius: 8px; padding: 22px 24px; font-family: 'JetBrains Mono', monospace; font-size: 0.875rem; line-height: 1.9; white-space: pre; overflow-x: auto; }
185
+ .tree .dir { color: var(--blue); font-weight: 600; }
186
+ .tree .file { color: #e6edf3; }
187
+ .tree .note { color: #8b949e; }
188
+ .tree .warn { color: var(--yellow); }
189
+ .tree .safe { color: var(--green); }
190
+
191
+ /* ---- STEP LIST ---- */
192
+ .steps { counter-reset: step; list-style: none; margin: 0 0 24px; padding: 0; }
193
+ .steps li { counter-increment: step; display: flex; gap: 18px; margin-bottom: 20px; }
194
+ .steps li::before {
195
+ content: counter(step);
196
+ flex-shrink: 0;
197
+ width: 30px; height: 30px;
198
+ background: var(--green-dim);
199
+ border: 1px solid rgba(0,157,108,.3);
200
+ color: var(--green);
201
+ border-radius: 50%;
202
+ display: flex; align-items: center; justify-content: center;
203
+ font-weight: 700; font-size: 0.85rem;
204
+ margin-top: 2px;
205
+ }
206
+
207
+ /* ---- SIGNATURE TABLE ---- */
208
+ .sig-table { width: 100%; border-collapse: collapse; margin: 16px 0 24px; font-size: 0.87rem; }
209
+ .sig-table th { text-align: left; padding: 10px 14px; background: var(--surface-2); color: var(--muted); font-weight: 600; border-bottom: 1px solid var(--border); }
210
+ .sig-table td { padding: 10px 14px; border-bottom: 1px solid rgba(48,54,61,.5); vertical-align: top; }
211
+ .sig-table td:first-child { font-family: 'JetBrains Mono', monospace; color: var(--yellow); white-space: nowrap; }
212
+ .sig-table td:last-child { color: var(--muted); }
213
+
214
+ /* ---- SCROLL SPY ---- */
215
+ header#page-header { margin-bottom: 52px; }
216
+ </style>
217
+ </head>
218
+ <body>
219
+
220
+ <!-- SIDEBAR -->
221
+ <aside class="sidebar">
222
+ <div class="sb-logo">
223
+ <strong>ArtiFrame</strong>
224
+ <span>Documentación para Desarrolladores</span>
225
+ </div>
226
+ <nav>
227
+ <div class="sb-cat">Inicio</div>
228
+ <a href="#giris">Introducción y Filosofía</a>
229
+ <a href="#kurulum">Instalación</a>
230
+ <a href="#dizin">Estructura de Directorios</a>
231
+
232
+ <div class="sb-cat">Arquitectura</div>
233
+ <a href="#bootstrapper">Arquitectura Bootstrapper</a>
234
+ <a href="#kurallar">Conjunto de Reglas</a>
235
+
236
+ <div class="sb-cat">Herramienta CLI</div>
237
+ <a href="#cli-giris">Introducción a la CLI</a>
238
+ <a href="#cli-new">new</a>
239
+ <a href="#cli-view">make:view</a>
240
+ <a href="#cli-api">make:api</a>
241
+ <a href="#cli-class">make:class</a>
242
+ <a href="#cli-version">version</a>
243
+
244
+ <div class="sb-cat">Ayudantes de Vista (View Helpers)</div>
245
+ <a href="#vh-display">display()</a>
246
+ <a href="#vh-csrf">csrfField()</a>
247
+ <a href="#vh-dates">Funciones de Fecha</a>
248
+ <a href="#vh-format">Formato</a>
249
+ <a href="#vh-money">money()</a>
250
+
251
+ <div class="sb-cat">Ayudantes de Sistema (System Helpers)</div>
252
+ <a href="#sh-json">jsonResponse()</a>
253
+ <a href="#sh-csrf">verifyCsrf()</a>
254
+ <a href="#sh-sanitize">Sanitización</a>
255
+ <a href="#sh-request">HTTP y Petición</a>
256
+ <a href="#sh-security">Seguridad</a>
257
+
258
+ <div class="sb-cat">Seguridad de API</div>
259
+ <a href="#api-methods">Control de Métodos HTTP</a>
260
+ <a href="#api-cors">CORS</a>
261
+ <a href="#api-rate">Limitación de Tasa (Rate Limiting)</a>
262
+
263
+ <div class="sb-cat">Práctica</div>
264
+ <a href="#workflow">Flujo de Trabajo Completo</a>
265
+ </nav>
266
+ </aside>
267
+
268
+ <!-- MAIN CONTENT -->
269
+ <main class="content">
270
+
271
+ <!-- ====== GİRİŞ ====== -->
272
+ <header id="page-header">
273
+ <h1>ArtiFrame <span>Documentación</span></h1>
274
+ <p class="lead">El framework de proyectos PHP nativos escalables administrados con cero dependencias externas, reglas estrictas y una poderosa CLI. Sin dependencias, libre del caos de paquetes de composer, un ecosistema ligero y rápido que le permite enfocarse en su lógica de negocio.</p>
275
+ </header>
276
+
277
+ <!-- ====== GİRİŞ & FELSEFE ====== -->
278
+ <section id="giris">
279
+ <h2>Introducción y Filosofía</h2>
280
+ <p>ArtiFrame se basa en el principio de <strong>"Convención sobre Configuración"</strong>. El poder de un framework no proviene de la riqueza de las herramientas que ofrece, sino de la coherencia del orden que establece.</p>
281
+
282
+ <div class="grid-2">
283
+ <div class="card highlight">
284
+ <h3>Cero Sobrecarga (Zero Overhead)</h3>
285
+ <p>Cero dependencias de paquetes de Composer, núcleo del framework o bibliotecas de terceros. Cada línea de código es suya; libre de inflamiento, libre de capas de abstracción innecesarias.</p>
286
+ </div>
287
+ <div class="card highlight">
288
+ <h3>La Seguridad es lo Primero</h3>
289
+ <p>La protección XSS, validación CSRF, sanitización contra inyección SQL y el control de métodos HTTP están integrados por defecto. La seguridad no es una opción, es un estándar.</p>
290
+ </div>
291
+ <div class="card highlight">
292
+ <h3>Conjunto Estricto de Reglas</h3>
293
+ <p>Un desarrollador junior recién incorporado al proyecto comprende la arquitectura <code class="ic">data-js</code> y la estructura de directorios en minutos. La armonía del equipo está garantizada a nivel del framework.</p>
294
+ </div>
295
+ <div class="card highlight">
296
+ <h3>CLI como Prioridad</h3>
297
+ <p>No se crean archivos de vista, API o clase manualmente. La CLI genera desde archivos stub, configura los enlaces de assets y mantiene el proyecto coherente.</p>
298
+ </div>
299
+ </div>
300
+
301
+ <div class="alert a-info">
302
+ <strong>ℹ️ Licencia AGPLv3:</strong> ArtiFrame es de código abierto. Los trabajos derivados que usted produzca pueden usarse libremente siempre que el código fuente permanezca abierto. El aviso de derechos de autor no puede eliminarse.
303
+ </div>
304
+ </section>
305
+
306
+ <!-- ====== KURULUM ====== -->
307
+ <section id="kurulum">
308
+ <h2>Instalación</h2>
309
+ <p>La CLI de ArtiFrame se instala como una herramienta PHP global. Se instala una vez, se usa en todos los proyectos.</p>
310
+
311
+ <h3>1. Instale la Herramienta CLI Globalmente</h3>
312
+ <pre><code><span class="cm"># Instalación global vía NPM</span>
313
+ npm install -g @artilingo/artiframe-cli
314
+
315
+ <span class="cm"># Verifique la instalación</span>
316
+ artiframe</code></pre>
317
+
318
+ <h3>2. Shell Interactivo</h3>
319
+ <p>Simplemente escriba <code class="ic">artiframe</code> en la terminal y presione Enter. La CLI no se cierra; se abre un shell interactivo que escucha comandos continuamente:</p>
320
+ <pre><code>==================================================
321
+ ArtiFrame CLI Interactive Shell v1.0.0
322
+ ==================================================
323
+ Type 'help' for commands, or 'exit' to quit.
324
+
325
+ artiframe&gt; </code></pre>
326
+
327
+ <h3>3. Iniciar un Nuevo Proyecto</h3>
328
+ <pre><code>artiframe&gt; new mi-proyecto</code></pre>
329
+ <p>Este comando crea el directorio <code class="ic">mi-proyecto/</code> y copia todo el esqueleto estructural dentro de él: <code class="ic">app/</code>, <code class="ic">bin/</code>, <code class="ic">config/</code>, <code class="ic">public/</code>, <code class="ic">src/</code>, <code class="ic">.env.example</code> y la primera página <code class="ic">index.php</code>.</p>
330
+
331
+ <h3>4. Configuración del Entorno</h3>
332
+ <pre><code>cp .env.example .env</code></pre>
333
+ <p>Abra su archivo <code class="ic">.env</code> y rellene la información de la base de datos y la aplicación. Este archivo nunca entra al control de versiones.</p>
334
+
335
+ <div class="alert a-warn">
336
+ <strong>⚠️ Configuración del Servidor Web:</strong> Apunte la raíz del documento (document root) de Apache/Nginx a la carpeta <code class="ic">/public/</code>. Otros directorios nunca deben estar expuestos al exterior.
337
+ </div>
338
+ </section>
339
+
340
+ <!-- ====== DİZİN YAPISI ====== -->
341
+ <section id="dizin">
342
+ <h2>Estructura de Directorios</h2>
343
+ <p>La arquitectura generada cuando se inicia el proyecto; asegura una separación clara de responsabilidades (SoC).</p>
344
+
345
+ <div class="tree">
346
+ <span class="dir">nombre-proyecto/</span>
347
+ ├── <span class="dir">app/</span> <span class="note"># Capa de infraestructura</span>
348
+ │ ├── <span class="file">ViewControl.php</span> <span class="note"># Bootstrapper para la Vista (página HTML)</span>
349
+ │ ├── <span class="file">ApiControl.php</span> <span class="note"># Bootstrapper para los endpoints API</span>
350
+ │ ├── <span class="file">Database.php</span> <span class="note"># Conexión de base de datos basada en PDO</span>
351
+ │ ├── <span class="file">DotEnv.php</span> <span class="note"># Lector de .env</span>
352
+ │ └── <span class="file">R2Manager.php</span> <span class="note"># Administrador de archivos Cloudflare R2</span>
353
+
354
+ ├── <span class="dir warn">bin/</span> <span class="note"># ⚠️ Sistema central — no lo edite directamente</span>
355
+ │ ├── <span class="file">SystemMethod.php</span> <span class="note"># Ayudantes globales de API/Backend</span>
356
+ │ ├── <span class="file">ViewMethod.php</span> <span class="note"># Ayudantes globales de View/Frontend</span>
357
+ │ └── <span class="dir">stubs/</span> <span class="note"># Archivos de plantilla usados por la CLI</span>
358
+ │ ├── view.stub
359
+ │ ├── api-standart.stub
360
+ │ ├── api-switch-case.stub
361
+ │ └── class.stub
362
+
363
+ ├── <span class="dir">config/</span> <span class="note"># Archivos de configuración</span>
364
+ │ └── <span class="file">app-version.php</span> <span class="note"># Constantes APP_VERSION y APP_ENV</span>
365
+
366
+ ├── <span class="dir">public/</span> <span class="note"># ← El único directorio abierto del servidor web</span>
367
+ │ ├── <span class="dir safe">assets/</span>
368
+ │ │ ├── <span class="dir">css/</span> <span class="note"># Archivos CSS específicos de la Vista</span>
369
+ │ │ └── <span class="dir">js/</span> <span class="note"># Archivos JS específicos de la Vista</span>
370
+ │ ├── <span class="dir">includes/</span> <span class="note"># Componentes compartidos</span>
371
+ │ │ ├── head.php
372
+ │ │ ├── header.php
373
+ │ │ └── footer.php
374
+ │ ├── <span class="dir">api/</span> <span class="note"># Archivos de endpoints API</span>
375
+ │ └── <span class="file">index.php</span> <span class="note"># Página principal</span>
376
+
377
+ ├── <span class="dir">src/</span> <span class="note"># Lógica de negocio y clases</span>
378
+ ├── <span class="file">.env</span> <span class="note"># Variables de entorno (no entra en git)</span>
379
+ ├── <span class="file">.env.example</span> <span class="note"># Plantilla — entra en git</span>
380
+ └── <span class="file">guia.html</span> <span class="note"># Este documento</span>
381
+ </div>
382
+
383
+ <div class="alert a-danger">
384
+ <strong>🚫 No toque el directorio bin/:</strong> Los archivos dentro de <code class="ic">bin/</code> son el núcleo del framework. No se añade lógica de negocio específica del proyecto aquí. Las clases y servicios que agregue van debajo de <code class="ic">src/</code>, los componentes de infraestructura van debajo de <code class="ic">app/</code>.
385
+ </div>
386
+ </section>
387
+
388
+ <!-- ====== BOOTSTRAPPER ====== -->
389
+ <section id="bootstrapper">
390
+ <h2>Arquitectura Bootstrapper <span class="tag b-core">Crítico</span></h2>
391
+ <p>ArtiFrame usa dos bootstrappers completamente independientes. Esta arquitectura previene inherentemente <strong>problemas de encabezados HTML y vulnerabilidades de seguridad</strong>.</p>
392
+
393
+ <div class="grid-2">
394
+ <div class="card highlight">
395
+ <h3><span class="badge b-view">View</span> ViewControl.php</h3>
396
+ <p>Se usa para páginas HTML (archivos view). Inicia sesión, carga <code class="ic">ViewMethod</code>.</p>
397
+ <pre><code><span class="cm">// public/perfil.php — HASTA ARRIBA, antes de imprimir HTML</span>
398
+ <span class="kw">&lt;?php</span>
399
+ <span class="fn">require_once</span> <span class="var">$_SERVER</span>[<span class="st">'DOCUMENT_ROOT'</span>]
400
+ . <span class="st">'/../app/ViewControl.php'</span>;
401
+ <span class="kw">use</span> Bin\ViewMethod;
402
+ <span class="kw">?&gt;</span>
403
+ <span class="kw">&lt;!DOCTYPE html&gt;</span>
404
+ ...</code></pre>
405
+ </div>
406
+ <div class="card highlight">
407
+ <h3><span class="badge b-api">API</span> ApiControl.php</h3>
408
+ <p>Se usa para archivos de endpoints API. Establece el encabezado JSON, comprueba el método HTTP, carga <code class="ic">SystemMethod</code>.</p>
409
+ <pre><code><span class="cm">// public/api/usuario/obtener.php</span>
410
+ <span class="kw">&lt;?php</span>
411
+ <span class="cm">// $allowedMethods DEBE definirse antes del require</span>
412
+ <span class="var">$allowedMethods</span> = [<span class="st">'GET'</span>];
413
+
414
+ <span class="fn">require_once</span> <span class="var">$_SERVER</span>[<span class="st">'DOCUMENT_ROOT'</span>]
415
+ . <span class="st">'/../app/ApiControl.php'</span>;
416
+
417
+ <span class="kw">use</span> Bin\SystemMethod;</code></pre>
418
+ </div>
419
+ </div>
420
+
421
+ <div class="alert a-danger">
422
+ <strong>🚫 ViewControl y ApiControl nunca se mezclan:</strong> Si ViewControl se requiere en un archivo de API, puede devolver un encabezado HTML en lugar de un encabezado JSON y corromper toda la respuesta de la API. Si ApiControl se requiere en una página HTML, la sesión no se inicia y la página se rompe.
423
+ </div>
424
+ </section>
425
+
426
+ <!-- ====== KURALLAR ====== -->
427
+ <section id="kurallar">
428
+ <h2>Conjunto de Reglas <span class="tag b-core">Estándar</span></h2>
429
+
430
+ <div class="card">
431
+ <h3>Regla 1: Arquitectura data-js <span class="badge b-sec">Crítico</span></h3>
432
+ <p>Los eventos de JavaScript nunca deben escucharse a través de un <code class="ic">class</code> o <code class="ic">id</code>. Estos son identificadores visuales / de estilo. Todas las interacciones de JS se manejan mediante el atributo <code class="ic">data-js</code>. Cuando el CSS elimina una clase, JavaScript nunca se rompe.</p>
433
+ <pre><code><span class="cm">&lt;!-- ❌ Anti-Patrón — no soportado --&gt;</span>
434
+ &lt;button id="submitBtn" class="btn"&gt;Enviar&lt;/button&gt;
435
+ <span class="cm">// JS: document.getElementById('submitBtn').addEventListener(...)</span>
436
+
437
+ <span class="cm">&lt;!-- ✅ Estándar ArtiFrame --&gt;</span>
438
+ &lt;button class="btn btn-primary" <span class="var">data-js</span>="login-submit"&gt;Enviar&lt;/button&gt;
439
+ <span class="cm">// JS: document.querySelector('[data-js="login-submit"]').addEventListener(...)</span></code></pre>
440
+ </div>
441
+
442
+ <div class="card">
443
+ <h3>Regla 2: Arquitectura de Temas</h3>
444
+ <p>El modo Oscuro/Claro y los temas se administran a través de los atributos <code class="ic">data-theme</code> y <code class="ic">data-mode</code> de la etiqueta <code class="ic">&lt;html&gt;</code>. No se utilizan clases en el body.</p>
445
+ <pre><code><span class="cm">&lt;!-- Etiqueta de apertura HTML de la plantilla view.stub --&gt;</span>
446
+ &lt;html lang="es" data-theme="default" data-mode="light"&gt;
447
+
448
+ <span class="cm">/* Definición del tema en app.css */</span>
449
+ html[data-theme="default"][data-mode="dark"] {
450
+ --bg-color: #0b0c0e;
451
+ --text-main: #ffffff;
452
+ }
453
+ html[data-theme="default"][data-mode="light"] {
454
+ --bg-color: #ffffff;
455
+ --text-main: #111111;
456
+ }</code></pre>
457
+ </div>
458
+
459
+ <div class="card">
460
+ <h3>Regla 3: Flujo de Datos Seguro</h3>
461
+ <p>Cada dato que proviene de la base de datos se envuelve con <code class="ic">display()</code> antes de imprimirse en el DOM. Cada dato que llega a la API se limpia con <code class="ic">sanitizeString()</code> o <code class="ic">sanitizeInt()</code> antes de ser procesado.</p>
462
+ </div>
463
+
464
+ <div class="card">
465
+ <h3>Regla 4: Manejo de Errores con APP_ENV</h3>
466
+ <p>El valor de <code class="ic">APP_ENV</code> en el archivo <code class="ic">.env</code> determina la visibilidad del error. En Producción (Production), no se muestran mensajes de error al usuario.</p>
467
+ <pre><code><span class="cm">// config/app-version.php</span>
468
+ define(<span class="st">'APP_ENV'</span>, (int)<span class="var">$_ENV</span>[<span class="st">'APP_ENV'</span>]); <span class="cm">// 1=Debug, 0=Production</span>
469
+
470
+ <span class="kw">if</span> (APP_ENV === <span class="nu">1</span>) {
471
+ ini_set(<span class="st">'display_errors'</span>, <span class="nu">1</span>);
472
+ error_reporting(E_ALL);
473
+ } <span class="kw">else</span> {
474
+ ini_set(<span class="st">'display_errors'</span>, <span class="nu">0</span>);
475
+ }</code></pre>
476
+ </div>
477
+ </section>
478
+
479
+ <!-- ====== CLI GİRİŞ ====== -->
480
+ <section id="cli-giris">
481
+ <h2>Ecosistema CLI</h2>
482
+ <p>La CLI de ArtiFrame abre un shell interactivo cuando escribe <code class="ic">artiframe</code> en la terminal. Todos los comandos se ejecutan dentro de este shell. Los comandos también se pueden ejecutar de forma puntual.</p>
483
+ <pre><code><span class="cm"># Modo interactivo (recomendado)</span>
484
+ artiframe
485
+ artiframe&gt; make:view admin/usuarios.php
486
+
487
+ <span class="cm"># Modo puntual (una sola vez)</span>
488
+ artiframe make:view admin/usuarios.php</code></pre>
489
+ </section>
490
+
491
+ <!-- new -->
492
+ <section id="cli-new">
493
+ <h2><code>new</code> <span class="tag b-cli">Comando CLI</span></h2>
494
+ <p>Crea un nuevo proyecto ArtiFrame. Crea todo el esqueleto de directorios, los archivos bootstrapper y la página index inicial.</p>
495
+ <pre><code>artiframe&gt; <span class="fn">new</span> <span class="st">nombre-proyecto</span></code></pre>
496
+ <p>Estructura creada:</p>
497
+ <div class="tree" style="font-size:0.82rem; line-height:1.7">
498
+ nombre-proyecto/
499
+ ├── app/ (ViewControl.php, ApiControl.php, Database.php, DotEnv.php)
500
+ ├── bin/ (SystemMethod.php, ViewMethod.php, stubs/)
501
+ ├── config/ (app-version.php)
502
+ ├── public/ (index.php, assets/, includes/, api/)
503
+ ├── src/
504
+ ├── .env.example
505
+ └── guia.html
506
+ </div>
507
+ </section>
508
+
509
+ <!-- make:view -->
510
+ <section id="cli-view">
511
+ <h2><code>make:view</code> <span class="tag b-cli">Comando CLI</span></h2>
512
+ <p>Crea un nuevo archivo de página (view) y sus recursos CSS/JS específicos. Los recursos se vinculan automáticamente a la página y se aplica la invalidación de caché (cache-busting) con <code class="ic">?v=APP_VERSION</code>.</p>
513
+ <pre><code>artiframe&gt; <span class="fn">make:view</span> <span class="st">admin/usuarios.php</span></code></pre>
514
+ <p>Archivos creados:</p>
515
+ <pre><code><span class="cm">✔ public/admin/usuarios.php</span>
516
+ <span class="cm">✔ public/assets/css/admin/usuarios.css</span>
517
+ <span class="cm">✔ public/assets/js/admin/usuarios.js</span></code></pre>
518
+ <p>El archivo de vista creado viene con ViewControl requerido al principio, los includes head/header/footer añadidos, y los enlaces CSS/JS conectados con cache-busting.</p>
519
+ </section>
520
+
521
+ <!-- make:api -->
522
+ <section id="cli-api">
523
+ <h2><code>make:api</code> <span class="tag b-cli">Comando CLI</span></h2>
524
+ <p>Crea un archivo de endpoint API eligiendo una de las dos plantillas diferentes. En cada nuevo archivo de API, la variable <code class="ic">$allowedMethods</code> y el requerimiento de <code class="ic">ApiControl.php</code> ya vienen preparados.</p>
525
+
526
+ <div class="card">
527
+ <h3><span class="badge b-api">standart</span> — API de Acción Única</h3>
528
+ <p>Para endpoints que realizan un solo trabajo (iniciar sesión, enviar, borrar). La lógica de negocio se escribe directamente.</p>
529
+ <pre><code>artiframe&gt; <span class="fn">make:api</span> <span class="st">standart</span> <span class="st">api/auth/entrar.php</span></code></pre>
530
+ <pre><code><span class="kw">&lt;?php</span>
531
+ <span class="var">$allowedMethods</span> = [<span class="st">'POST'</span>]; <span class="cm">// Aceptar solo POST</span>
532
+ <span class="fn">require_once</span> <span class="var">$_SERVER</span>[<span class="st">'DOCUMENT_ROOT'</span>] . <span class="st">'/../app/ApiControl.php'</span>;
533
+
534
+ <span class="kw">use</span> Bin\SystemMethod;
535
+
536
+ <span class="cm">// Lógica de negocio aquí...</span>
537
+ <span class="fn">jsonResponse</span>([<span class="st">'status'</span> =&gt; <span class="st">'success'</span>], <span class="nu">200</span>);</code></pre>
538
+ </div>
539
+
540
+ <div class="card">
541
+ <h3><span class="badge b-api">switch-case</span> — API de Múltiples Acciones</h3>
542
+ <p>Una estructura que maneja operaciones CRUD para un módulo en un único endpoint. Qué acción se debe realizar se determina por el parámetro <code class="ic">action</code>.</p>
543
+ <pre><code>artiframe&gt; <span class="fn">make:api</span> <span class="st">switch-case</span> <span class="st">api/usuario/administrar.php</span></code></pre>
544
+ <pre><code><span class="kw">&lt;?php</span>
545
+ <span class="var">$allowedMethods</span> = [<span class="st">'POST'</span>];
546
+ <span class="fn">require_once</span> <span class="var">$_SERVER</span>[<span class="st">'DOCUMENT_ROOT'</span>] . <span class="st">'/../app/ApiControl.php'</span>;
547
+
548
+ <span class="kw">use</span> Bin\SystemMethod;
549
+
550
+ <span class="var">$action</span> = <span class="fn">sanitizeString</span>(<span class="var">$_POST</span>[<span class="st">'action'</span>] ?? <span class="st">''</span>);
551
+
552
+ <span class="kw">switch</span> (<span class="var">$action</span>) {
553
+ <span class="kw">case</span> <span class="st">'create'</span>:
554
+ <span class="fn">jsonResponse</span>([<span class="st">'status'</span> =&gt; <span class="st">'success'</span>, <span class="st">'message'</span> =&gt; <span class="st">'Creado.'</span>], <span class="nu">200</span>);
555
+ <span class="kw">break</span>;
556
+ <span class="kw">case</span> <span class="st">'update'</span>:
557
+ <span class="fn">jsonResponse</span>([<span class="st">'status'</span> =&gt; <span class="st">'success'</span>, <span class="st">'message'</span> =&gt; <span class="st">'Actualizado.'</span>], <span class="nu">200</span>);
558
+ <span class="kw">break</span>;
559
+ <span class="kw">case</span> <span class="st">'delete'</span>:
560
+ <span class="fn">jsonResponse</span>([<span class="st">'status'</span> =&gt; <span class="st">'success'</span>, <span class="st">'message'</span> =&gt; <span class="st">'Eliminado.'</span>], <span class="nu">200</span>);
561
+ <span class="kw">break</span>;
562
+ <span class="kw">default</span>:
563
+ <span class="fn">jsonResponse</span>([<span class="st">'status'</span> =&gt; <span class="st">'error'</span>, <span class="st">'message'</span> =&gt; <span class="st">'Acción no válida.'</span>], <span class="nu">400</span>);
564
+ }</code></pre>
565
+ </div>
566
+ </section>
567
+
568
+ <!-- make:class -->
569
+ <section id="cli-class">
570
+ <h2><code>make:class</code> <span class="tag b-cli">Comando CLI</span></h2>
571
+ <p>Crea un nuevo archivo de clase PHP con el namespace y la estructura básica (boilerplate) preparados.</p>
572
+ <pre><code>artiframe&gt; <span class="fn">make:class</span> <span class="st">classes/EmailService.php</span></code></pre>
573
+ </section>
574
+
575
+ <!-- version -->
576
+ <section id="cli-version">
577
+ <h2><code>version</code> <span class="tag b-cli">Comando CLI</span></h2>
578
+ <p>Actualiza el número de versión en <code class="ic">config/app-version.php</code> de acuerdo a las reglas de versionamiento semántico (SemVer). Formato de versión: <strong>MAYOR.MENOR.PARCHE</strong></p>
579
+
580
+ <table class="sig-table">
581
+ <thead>
582
+ <tr><th>Comando</th><th>Descripción</th><th>Ejemplo</th></tr>
583
+ </thead>
584
+ <tbody>
585
+ <tr><td>version upgrade patch</td><td>Corrección de errores, mejora menor</td><td>1.2.3 → 1.2.4</td></tr>
586
+ <tr><td>version upgrade minor</td><td>Nueva característica compatible con versiones anteriores</td><td>1.2.3 → 1.3.0</td></tr>
587
+ <tr><td>version upgrade major</td><td>Cambio importante que rompe compatibilidad</td><td>1.2.3 → 2.0.0</td></tr>
588
+ <tr><td>version downgrade patch</td><td>Deshacer el último parche</td><td>1.2.4 → 1.2.3</td></tr>
589
+ <tr><td>version downgrade minor</td><td>Deshacer el último menor</td><td>1.3.0 → 1.2.0</td></tr>
590
+ <tr><td>version downgrade major</td><td>Deshacer el último mayor</td><td>2.0.0 → 1.0.0</td></tr>
591
+ </tbody>
592
+ </table>
593
+
594
+ <pre><code>artiframe&gt; <span class="fn">version</span> upgrade minor
595
+ <span class="cm">✔ Versión actualizada a 1.2.0 → 1.3.0.</span></code></pre>
596
+ </section>
597
+
598
+ <!-- ====== VIEW HELPERS ====== -->
599
+ <section id="vh-display">
600
+ <h2>Ayudantes de Vista (View Helpers) — <code>display()</code> <span class="tag b-view">ViewMethod</span></h2>
601
+ <p>Aplica una <strong>protección XSS obligatoria</strong> mientras imprime datos provenientes de la base de datos o entradas del usuario al DOM. En lugar de lanzar un error en valores nulos o vacíos, devuelve un valor predeterminado.</p>
602
+ <span class="ret">string display(mixed $data, string $default = '')</span>
603
+ <div class="alert a-info" style="margin-top: 10px; margin-bottom: 24px; padding: 12px 20px;">
604
+ <strong>💡 Consejo: Uso de <code>$default</code></strong><br>
605
+ El segundo parámetro (<code>$default</code>) es el texto de rescate que se mostrará en pantalla cuando los datos que provienen de la base de datos estén vacíos (nulo, falso, cadena vacía). Por ejemplo, si utiliza <code>display($nombre, 'Anónimo')</code> para un usuario cuyo nombre no se ha introducido, su página presentará contenido significativo en lugar de horribles espacios en blanco.
606
+ </div>
607
+ <pre><code><span class="cm">&lt;!-- ❌ Inseguro — crea una vulnerabilidad XSS --&gt;</span>
608
+ &lt;h1&gt;&lt;?= <span class="var">$user</span>[<span class="st">'nombre'</span>] ?&gt;&lt;/h1&gt;
609
+
610
+ <span class="cm">&lt;!-- ✅ Uso Seguro de ArtiFrame --&gt;</span>
611
+ &lt;h1&gt;&lt;?= <span class="fn">display</span>(<span class="var">$user</span>[<span class="st">'nombre'</span>], <span class="st">'Usuario Anónimo'</span>) ?&gt;&lt;/h1&gt;</code></pre>
612
+ </section>
613
+
614
+ <section id="vh-escapeurl">
615
+ <h2>Ayudantes de Vista (View Helpers) — <code>escapeUrl()</code> <span class="tag b-view">ViewMethod</span></h2>
616
+ <p>Se utiliza al insertar enlaces recibidos de los usuarios (como sitios web de perfil) dentro de un <code>&lt;a href="..."&gt;</code> o <code>&lt;img src="..."&gt;</code>. Neutraliza cargas útiles (payloads) maliciosas como <code>javascript:alert(1)</code> (XSS Almacenado) y evita la ejecución de código a través de enlaces.</p>
617
+ <span class="ret">string escapeUrl(string $url)</span>
618
+ <pre><code><span class="cm">&lt;!-- ❌ Inseguro — se puede filtrar código JS a través de la URL --&gt;</span>
619
+ &lt;a href="&lt;?= <span class="var">$user</span>[<span class="st">'website'</span>] ?&gt;"&gt;Visitar Sitio&lt;/a&gt;
620
+
621
+ <span class="cm">&lt;!-- ✅ Uso Seguro de ArtiFrame --&gt;</span>
622
+ &lt;a href="&lt;?= <span class="fn">escapeUrl</span>(<span class="var">$user</span>[<span class="st">'website'</span>]) ?&gt;"&gt;Visitar Sitio&lt;/a&gt;</code></pre>
623
+ </section>
624
+
625
+ <section id="vh-csrf">
626
+ <h2>Ayudantes de Vista (View Helpers) — <code>csrfField()</code> <span class="tag b-view">ViewMethod</span></h2>
627
+ <p>Añade un campo de token oculto a los formularios HTML contra ataques CSRF. <strong>Es obligatorio su uso en todas las páginas que contengan formularios POST.</strong></p>
628
+ <span class="ret">string csrfField()</span>
629
+ <pre><code>&lt;form action="/api/guardar.php" method="POST"&gt;
630
+ &lt;?= <span class="fn">csrfField</span>() ?&gt; <span class="cm">&lt;!-- Es obligatorio por seguridad --&gt;</span>
631
+ &lt;input type="text" name="nombre"&gt;
632
+ &lt;button type="submit" data-js="btn-guardar"&gt;Guardar&lt;/button&gt;
633
+ &lt;/form&gt;</code></pre>
634
+ </section>
635
+
636
+ <section id="vh-dates">
637
+ <h2>Ayudantes de Vista (View Helpers) — Funciones de Fecha <span class="tag b-view">ViewMethod</span></h2>
638
+ <p>Procesa cadenas de fecha en formato <code class="ic">Y-m-d H:i:s</code> de la base de datos o valores de marca de tiempo (timestamp) de UNIX. Todas las salidas textuales están localizadas según el idioma (<code class="ic">tr</code>, <code class="ic">en</code>, <code class="ic">de</code>, <code class="ic">fr</code>, <code class="ic">es</code>).</p>
639
+
640
+ <table class="sig-table">
641
+ <thead><tr><th>Función</th><th>Ejemplo de Salida</th><th>Descripción</th></tr></thead>
642
+ <tbody>
643
+ <tr><td>day($date)</td><td>24</td><td>Solo día</td></tr>
644
+ <tr><td>month($date)</td><td>07</td><td>Solo mes (número)</td></tr>
645
+ <tr><td>year($date)</td><td>2026</td><td>Año</td></tr>
646
+ <tr><td>timeOnly($date)</td><td>14:30</td><td>Hora:Minuto</td></tr>
647
+ <tr><td>fulldate($date)</td><td>24.07.2026</td><td>Fecha completa</td></tr>
648
+ <tr><td>formatDate($date, $format)</td><td>24.07.2026 14:30</td><td>Formato personalizado</td></tr>
649
+ <tr><td>monthName($date, $lang)</td><td>Julio / July / Juli</td><td>Nombre del mes (según el idioma)</td></tr>
650
+ <tr><td>fulldateName($date, $lang)</td><td>24 de julio de 2026 / July 24, 2026</td><td>Fecha completa (con el nombre del mes, según el idioma)</td></tr>
651
+ <tr><td>timeAgo($date, $lang)</td><td>Hace 5 minutos / 5 minutes ago</td><td>Estilo de redes sociales (según el idioma)</td></tr>
652
+ </tbody>
653
+ </table>
654
+
655
+ <div class="alert a-info" style="margin-top: 0; margin-bottom: 24px;">
656
+ <strong>💡 Entendiendo los Parámetros: <code>$format</code> y <code>$lang</code></strong>
657
+ <ul style="margin-top: 10px; margin-bottom: 0;">
658
+ <li style="margin-bottom: 8px;"><strong><code>$format</code> (Formato):</strong> Se usa únicamente con la función <code>formatDate()</code>. Acepta letras de formato de fecha PHP estándar. (Ejemplo: <code>'d/m/Y'</code> ➔ 24/07/2026, o <code>'H:i'</code> ➔ 15:30). Permite crear su propio patrón de fecha cuando otras funciones preparadas (day, year, etc.) no son suficientes.</li>
659
+ <li style="margin-bottom: 0;"><strong><code>$lang</code> (Idioma):</strong> Se usa en funciones que contienen texto en su salida (nombre del mes, la palabra "hace"). Si deja este parámetro vacío, el sistema opera <strong>por defecto en <code>'tr'</code> (Turco)</strong>. Si está haciendo un proyecto multilingüe, simplemente ingrese el código del idioma (<code>tr, en, de, fr, es</code>) en el segundo parámetro. (Ej: <code>timeAgo($fecha, 'es')</code> ➔ hace 5 minutos)</li>
660
+ </ul>
661
+ </div>
662
+
663
+ <pre><code><span class="cm">&lt;!-- Supongamos que es una marca de tiempo UNIX obtenida con la función time() --&gt;</span>
664
+ <span class="kw">&lt;?php</span> <span class="var">$fecha</span> = <span class="fn">time</span>(); <span class="kw">?&gt;</span>
665
+
666
+ <span class="cm">&lt;!-- Fecha completa según el idioma --&gt;</span>
667
+ &lt;span&gt;&lt;?= <span class="fn">fulldateName</span>(<span class="var">$fecha</span>, <span class="st">'es'</span>) ?&gt;&lt;/span&gt;
668
+ <span class="cm">&lt;!-- Salida: 24 de julio de 2026 --&gt;</span>
669
+
670
+ <span class="cm">&lt;!-- Representación de tiempo estilo redes sociales --&gt;</span>
671
+ &lt;span&gt;&lt;?= <span class="fn">timeAgo</span>(<span class="var">$fecha</span>, <span class="st">'en'</span>) ?&gt;&lt;/span&gt;
672
+ <span class="cm">&lt;!-- Salida: 5 minutes ago --&gt;</span>
673
+
674
+ &lt;span&gt;&lt;?= <span class="fn">timeAgo</span>(<span class="var">$fecha</span>, <span class="st">'de'</span>) ?&gt;&lt;/span&gt;
675
+ <span class="cm">&lt;!-- Salida: vor 5 Minuten --&gt;</span></code></pre>
676
+ </section>
677
+
678
+ <section id="vh-format">
679
+ <h2>Ayudantes de Vista (View Helpers) — Formateo de Texto <span class="tag b-view">ViewMethod</span></h2>
680
+
681
+ <div class="card">
682
+ <h3><code>truncate($text, $length, $append)</code></h3>
683
+ <span class="ret">string truncate(string $text, int $length = 100, string $append = '...')</span>
684
+ <p>Recorta textos largos (por ejemplo, resúmenes de blog) en el límite de caracteres deseado sin cortar la última palabra y agrega el sufijo especificado al final.</p>
685
+ <pre><code>&lt;p&gt;&lt;?= <span class="fn">truncate</span>(<span class="var">$post</span>[<span class="st">'contenido'</span>], <span class="nu">160</span>, <span class="st">'...'</span>) ?&gt;&lt;/p&gt;</code></pre>
686
+ </div>
687
+ </section>
688
+
689
+ <section id="vh-money">
690
+ <h2>Ayudantes de Vista (View Helpers) — <code>money()</code> <span class="tag b-view">ViewMethod</span></h2>
691
+ <p>Formatea la cantidad con el símbolo de moneda. El código de la moneda (ISO) determina automáticamente la ubicación del símbolo. La moneda por defecto es <code class="ic">usd</code>.</p>
692
+ <span class="ret">string money(float $amount, string $currency = 'usd')</span>
693
+
694
+ <table class="sig-table">
695
+ <thead><tr><th>Código</th><th>Salida</th><th>Moneda</th></tr></thead>
696
+ <tbody>
697
+ <tr><td>usd</td><td>$1.250,00</td><td>Dólar estadounidense</td></tr>
698
+ <tr><td>eur</td><td>1.250,00 €</td><td>Euro</td></tr>
699
+ <tr><td>try / tl</td><td>1.250,00 ₺</td><td>Lira Turca</td></tr>
700
+ <tr><td>gbp</td><td>£1.250,00</td><td>Libra Esterlina Británica</td></tr>
701
+ <tr><td>jpy</td><td>1.250,00 ¥</td><td>Yen Japonés</td></tr>
702
+ <tr><td>inr</td><td>1.250,00 ₹</td><td>Rupia India</td></tr>
703
+ <tr><td>rub</td><td>1.250,00 ₽</td><td>Rublo Ruso</td></tr>
704
+ <tr><td>krw</td><td>1.250,00 ₩</td><td>Won Surcoreano</td></tr>
705
+ <tr><td>brl</td><td>R$1.250,00</td><td>Real Brasileño</td></tr>
706
+ <tr><td>aed</td><td>1.250,00 د.إ</td><td>Dírham de EAU</td></tr>
707
+ </tbody>
708
+ </table>
709
+
710
+ <pre><code>&lt;span&gt;&lt;?= <span class="fn">money</span>(<span class="var">$producto</span>[<span class="st">'precio'</span>], <span class="st">'try'</span>) ?&gt;&lt;/span&gt;
711
+ <span class="cm">&lt;!-- Salida: 1.250,00 ₺ --&gt;</span>
712
+
713
+ &lt;span&gt;&lt;?= <span class="fn">money</span>(<span class="var">$producto</span>[<span class="st">'precio'</span>], <span class="st">'usd'</span>) ?&gt;&lt;/span&gt;
714
+ <span class="cm">&lt;!-- Salida: $1.250,00 --&gt;</span></code></pre>
715
+ </section>
716
+
717
+ <!-- ====== SYSTEM HELPERS ====== -->
718
+ <section id="sh-json">
719
+ <h2>Ayudantes del Sistema (System Helpers) — <code>jsonResponse()</code> <span class="tag b-api">SystemMethod</span></h2>
720
+ <p>Configura el encabezado JSON, proporciona el código de estado HTTP y emite la salida de manera segura con json_encode, luego termina el script. <strong>Todas las respuestas de API deben darse a través de esta función.</strong></p>
721
+ <span class="ret">void jsonResponse(array $data, int $statusCode = 200)</span>
722
+ <pre><code><span class="cm">// Respuesta exitosa</span>
723
+ <span class="fn">jsonResponse</span>([<span class="st">'status'</span> =&gt; <span class="st">'success'</span>, <span class="st">'data'</span> =&gt; <span class="var">$usuario</span>], <span class="nu">200</span>);
724
+
725
+ <span class="cm">// Respuesta de error</span>
726
+ <span class="fn">jsonResponse</span>([<span class="st">'status'</span> =&gt; <span class="st">'error'</span>, <span class="st">'message'</span> =&gt; <span class="st">'Acceso no autorizado.'</span>], <span class="nu">401</span>);</code></pre>
727
+ </section>
728
+
729
+ <section id="sh-csrf">
730
+ <h2>Ayudantes del Sistema (System Helpers) — <code>verifyCsrf()</code> <span class="tag b-api">SystemMethod</span></h2>
731
+ <p>Verifica que la solicitud POST entrante proviene de un formulario legítimo. Si no se agregó <code class="ic">csrfField()</code> dentro del formulario o si el token no es válido, devuelve false.</p>
732
+ <span class="ret">bool verifyCsrf(string $token)</span>
733
+ <pre><code><span class="kw">if</span> (!<span class="fn">verifyCsrf</span>(<span class="var">$_POST</span>[<span class="st">'csrf_token'</span>] ?? <span class="st">''</span>)) {
734
+ <span class="fn">jsonResponse</span>([<span class="st">'status'</span> =&gt; <span class="st">'error'</span>, <span class="st">'message'</span> =&gt; <span class="st">'Token CSRF no válido.'</span>], <span class="nu">403</span>);
735
+ }</code></pre>
736
+ </section>
737
+
738
+ <section id="sh-sanitize">
739
+ <h2>Ayudantes del Sistema (System Helpers) — Funciones de Sanitización <span class="tag b-api">SystemMethod</span></h2>
740
+
741
+ <table class="sig-table">
742
+ <thead><tr><th>Función</th><th>Descripción</th></tr></thead>
743
+ <tbody>
744
+ <tr><td>sanitizeInt($value)</td><td>Elimina todas las letras, símbolos y comas que contiene, dejando <strong>solo números enteros</strong>. Utilizado para ID o límites.</td></tr>
745
+ <tr><td>sanitizeFloat($value)</td><td>Limpia todo excepto números decimales (fraccionarios). Utilizado para montos de dinero o métricas.</td></tr>
746
+ <tr><td>sanitizeString($value)</td><td>Destruye todas las <strong>etiquetas HTML y PHP (<code>&lt;script&gt;</code>, <code>&lt;iframe&gt;</code>, etc.)</strong> para evitar ataques XSS y derivados. Deja un texto sin formato y seguro.</td></tr>
747
+ <tr><td>sanitizeEmail($email)</td><td>Filtra todos los caracteres inválidos y peligrosos que no se ajustan al formato de correo electrónico.</td></tr>
748
+ </tbody>
749
+ </table>
750
+
751
+ <pre><code><span class="var">$id</span> = <span class="fn">sanitizeInt</span>(<span class="var">$_POST</span>[<span class="st">'id'</span>] ?? <span class="nu">0</span>);
752
+ <span class="var">$nombre</span>= <span class="fn">sanitizeString</span>(<span class="var">$_POST</span>[<span class="st">'nombre'</span>] ?? <span class="st">''</span>);
753
+ <span class="var">$email</span> = <span class="fn">sanitizeEmail</span>(<span class="var">$_POST</span>[<span class="st">'email'</span>] ?? <span class="st">''</span>);</code></pre>
754
+ </section>
755
+
756
+ <section id="sh-request">
757
+ <h2>Ayudantes del Sistema (System Helpers) — HTTP y Petición <span class="tag b-api">SystemMethod</span></h2>
758
+
759
+ <table class="sig-table">
760
+ <thead><tr><th>Función</th><th>Descripción</th></tr></thead>
761
+ <tbody>
762
+ <tr><td>isAjax()</td><td>Comprueba el encabezado <code>HTTP_X_REQUESTED_WITH</code> para verificar si la solicitud vino de Fetch API / XMLHttpRequest.</td></tr>
763
+ <tr><td>getClientIp()</td><td>Captura de forma segura la dirección IP real del usuario (incluso si está detrás de proxies o Cloudflare).</td></tr>
764
+ <tr><td>redirect($url)</td><td>Crea un encabezado de Location y finaliza de inmediato la ejecución.</td></tr>
765
+ </tbody>
766
+ </table>
767
+
768
+ <pre><code><span class="cm">// Bloquear acceso directo desde el navegador al endpoint API</span>
769
+ <span class="kw">if</span> (!<span class="fn">isAjax</span>()) {
770
+ <span class="fn">jsonResponse</span>([<span class="st">'error'</span> =&gt; <span class="st">'El acceso directo está restringido.'</span>], <span class="nu">403</span>);
771
+ }
772
+
773
+ <span class="cm">// Redirigir si no hay sesión</span>
774
+ <span class="kw">if</span> (!isset(<span class="var">$_SESSION</span>[<span class="st">'user'</span>])) {
775
+ <span class="fn">redirect</span>(<span class="st">'/login.php'</span>);
776
+ }
777
+
778
+ <span class="var">$ip</span> = <span class="fn">getClientIp</span>(); <span class="cm">// IP real incluso detrás de Cloudflare</span></code></pre>
779
+ </section>
780
+
781
+ <section id="sh-security">
782
+ <h2>Ayudantes del Sistema (System Helpers) — Seguridad <span class="tag b-sec">SystemMethod</span></h2>
783
+
784
+ <table class="sig-table">
785
+ <thead><tr><th>Función</th><th>Descripción</th></tr></thead>
786
+ <tbody>
787
+ <tr><td>generateCsrf()</td><td>Genera un nuevo token CSRF y lo guarda en la sesión</td></tr>
788
+ <tr><td>generateToken($length)</td><td>Cadena hex aleatoria segura criptográficamente (restablecimiento de contraseña, clave API, etc.)</td></tr>
789
+ <tr><td>hashPassword($password)</td><td>Hashea la contraseña con bcrypt</td></tr>
790
+ <tr><td>verifyPassword($password, $hash)</td><td>Verifica la contraseña con el hash</td></tr>
791
+ </tbody>
792
+ </table>
793
+
794
+ <pre><code><span class="cm">// Generación de token seguro (clave API, enlace de verificación de correo electrónico, etc.)</span>
795
+ <span class="var">$token</span> = <span class="fn">generateToken</span>(<span class="nu">32</span>);
796
+ <span class="cm">// ⚠️ Nota: Se generan 32 bytes de datos, pero debido a que se convierte al formato</span>
797
+ <span class="cm">// hexadecimal (base 16), la salida es exactamente el doble, es decir, una cadena de 64 caracteres de largo.</span>
798
+
799
+ <span class="cm">// Registro de contraseña</span>
800
+ <span class="var">$hash</span> = <span class="fn">hashPassword</span>(<span class="var">$_POST</span>[<span class="st">'clave'</span>]);
801
+
802
+ <span class="cm">// Verificación de contraseña</span>
803
+ <span class="kw">if</span> (!<span class="fn">verifyPassword</span>(<span class="var">$_POST</span>[<span class="st">'clave'</span>], <span class="var">$usuario</span>[<span class="st">'clave_hash'</span>])) {
804
+ <span class="fn">jsonResponse</span>([<span class="st">'error'</span> =&gt; <span class="st">'Contraseña incorrecta.'</span>], <span class="nu">401</span>);
805
+ }</code></pre>
806
+ </section>
807
+
808
+ <!-- ====== API GÜVENLİĞİ ====== -->
809
+ <section id="api-methods">
810
+ <h2>API — Control de Métodos HTTP <span class="tag b-sec">ApiControl</span></h2>
811
+ <p>El arreglo <code class="ic">$allowedMethods</code> debe definirse <strong>antes</strong> del require de ApiControl. Al leer este arreglo, ApiControl rechaza automáticamente las solicitudes de los métodos no permitidos con un <code class="ic">405 Method Not Allowed</code>.</p>
812
+ <pre><code><span class="cm">// Un endpoint que solo acepta GET y POST</span>
813
+ <span class="var">$allowedMethods</span> = [<span class="st">'GET'</span>, <span class="st">'POST'</span>];
814
+ <span class="fn">require_once</span> ... . <span class="st">'/../app/ApiControl.php'</span>;
815
+
816
+ <span class="cm">// Un endpoint que solo acepta DELETE</span>
817
+ <span class="var">$allowedMethods</span> = [<span class="st">'DELETE'</span>];
818
+ <span class="fn">require_once</span> ... . <span class="st">'/../app/ApiControl.php'</span>;</code></pre>
819
+ <p>Las solicitudes de preflight <code class="ic">OPTIONS</code> devuelven automáticamente <code class="ic">200</code> para CORS y el script termina — no es necesaria ninguna intervención manual.</p>
820
+ </section>
821
+
822
+ <section id="api-cors">
823
+ <h2>API — CORS <span class="tag b-api">ApiControl (Comentado)</span></h2>
824
+ <p>Si desea permitir el acceso a la API desde otros dominios o aplicaciones móviles, active el bloque CORS en <code class="ic">ApiControl.php</code>.</p>
825
+ <pre><code><span class="cm">// Dentro de ApiControl.php — se activa eliminando el comentario</span>
826
+ header(<span class="st">"Access-Control-Allow-Origin: https://su-dominio.com"</span>);
827
+ header(<span class="st">"Access-Control-Allow-Methods: GET, POST, OPTIONS"</span>);
828
+ header(<span class="st">"Access-Control-Allow-Headers: Content-Type, Authorization"</span>);</code></pre>
829
+ <div class="alert a-warn">
830
+ <strong>⚠️</strong> No utilice <code class="ic">*</code> (todos los dominios) en un entorno de Producción. Especifique explícitamente la dirección del dominio.
831
+ </div>
832
+ </section>
833
+
834
+ <section id="api-rate">
835
+ <h2>API — Limitación de Tasa (Rate Limiting) <span class="tag b-api">ApiControl (Comentado)</span></h2>
836
+ <p>Para evitar que usuarios malintencionados o bots inunden (flood) la API, puede activar el bloque de Rate Limiting dentro de <code class="ic">ApiControl.php</code>. La regla por defecto es: <strong>60 peticiones en 60 segundos por IP</strong>.</p>
837
+ <div class="alert a-warn" style="margin-top: 10px; margin-bottom: 20px;">
838
+ <strong>⚠️ Servidor Redis Requerido:</strong> La función de Limitación de Tasa (Rate Limiting) opera usando Redis a través de la clase <code>\Src\Service\RedisService::getInstance()</code>. Antes de descomentar este bloque de código, asegúrese de haber instalado un servicio Redis en su proyecto y haber creado una clase de conexión bajo <code>src/Service/</code>.
839
+ </div>
840
+ <pre><code><span class="cm">// Si hay más de 60 peticiones en 60 segundos de la misma IP:</span>
841
+ http_response_code(<span class="nu">429</span>); <span class="cm">// Demasiadas Peticiones</span>
842
+ echo json_encode([<span class="st">'error'</span> =&gt; <span class="st">'Demasiadas peticiones. Por favor, espere.'</span>]);</code></pre>
843
+ </section>
844
+
845
+ <!-- ====== WORKFLOW ====== -->
846
+ <section id="workflow">
847
+ <h2>Flujo de Trabajo Completo: Formulario de Contacto (De Extremo a Extremo)</h2>
848
+ <p>Examinemos paso a paso cómo desarrollar una función de <strong>Formulario de Contacto</strong> con los estándares de ArtiFrame de principio a fin.</p>
849
+
850
+ <ol class="steps">
851
+ <li>
852
+ <div>
853
+ <strong>Cree el Archivo de Vista (View)</strong>
854
+ <pre><code>artiframe&gt; <span class="fn">make:view</span> <span class="st">contacto.php</span>
855
+ <span class="cm">✔ public/contacto.php</span>
856
+ <span class="cm">✔ public/assets/css/contacto.css</span>
857
+ <span class="cm">✔ public/assets/js/contacto.js</span></code></pre>
858
+ </div>
859
+ </li>
860
+ <li>
861
+ <div>
862
+ <strong>Cree un Endpoint API</strong>
863
+ <pre><code>artiframe&gt; <span class="fn">make:api</span> <span class="st">standart</span> <span class="st">api/contacto/enviar.php</span></code></pre>
864
+ </div>
865
+ </li>
866
+ <li>
867
+ <div>
868
+ <strong>Codifique el Formulario HTML</strong><br>
869
+ Añada el siguiente formulario al archivo <code class="ic">public/contacto.php</code>:
870
+ <pre><code>&lt;form action="/api/contacto/enviar.php" method="POST"&gt;
871
+ &lt;?= <span class="fn">csrfField</span>() ?&gt;
872
+ &lt;input type="text" name="nombre" placeholder="Su Nombre"&gt;
873
+ &lt;input type="email" name="email" placeholder="Correo Electrónico"&gt;
874
+ &lt;textarea name="mensaje" placeholder="Su Mensaje"&gt;&lt;/textarea&gt;
875
+ &lt;button type="submit" data-js="<span class="st">contacto-enviar</span>"&gt;Enviar&lt;/button&gt;
876
+ &lt;/form&gt;</code></pre>
877
+ </div>
878
+ </li>
879
+ <li>
880
+ <div>
881
+ <strong>Conecte la API de Fetch con JavaScript</strong><br>
882
+ En el archivo <code class="ic">public/assets/js/contacto.js</code>:
883
+ <pre><code>document.<span class="fn">querySelector</span>(<span class="st">'[data-js="contacto-enviar"]'</span>).<span class="fn">addEventListener</span>(<span class="st">'click'</span>, async (e) =&gt; {
884
+ e.<span class="fn">preventDefault</span>();
885
+ <span class="kw">const</span> formData = <span class="kw">new</span> <span class="fn">FormData</span>(e.target.<span class="fn">closest</span>(<span class="st">'form'</span>));
886
+ <span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="st">'/api/contacto/enviar.php'</span>, {
887
+ method: <span class="st">'POST'</span>,
888
+ body: formData
889
+ });
890
+ <span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
891
+ <span class="fn">console</span>.<span class="fn">log</span>(data);
892
+ });</code></pre>
893
+ </div>
894
+ </li>
895
+ <li>
896
+ <div>
897
+ <strong>Complete la Lógica del Servidor (Backend)</strong><br>
898
+ En el archivo <code class="ic">public/api/contacto/enviar.php</code>:
899
+ <pre><code><span class="kw">&lt;?php</span>
900
+ <span class="var">$allowedMethods</span> = [<span class="st">'POST'</span>];
901
+ <span class="fn">require_once</span> <span class="var">$_SERVER</span>[<span class="st">'DOCUMENT_ROOT'</span>] . <span class="st">'/../app/ApiControl.php'</span>;
902
+ <span class="kw">use</span> Bin\SystemMethod;
903
+
904
+ <span class="cm">// 1. Validación CSRF</span>
905
+ <span class="kw">if</span> (!<span class="fn">verifyCsrf</span>(<span class="var">$_POST</span>[<span class="st">'csrf_token'</span>] ?? <span class="st">''</span>)) {
906
+ <span class="fn">jsonResponse</span>([<span class="st">'error'</span> =&gt; <span class="st">'Token inválido.'</span>], <span class="nu">403</span>);
907
+ }
908
+
909
+ <span class="cm">// 2. Sanitización de Datos</span>
910
+ <span class="var">$nombre</span> = <span class="fn">sanitizeString</span>(<span class="var">$_POST</span>[<span class="st">'nombre'</span>] ?? <span class="st">''</span>);
911
+ <span class="var">$email</span> = <span class="fn">sanitizeEmail</span>(<span class="var">$_POST</span>[<span class="st">'email'</span>] ?? <span class="st">''</span>);
912
+ <span class="var">$mensaje</span> = <span class="fn">sanitizeString</span>(<span class="var">$_POST</span>[<span class="st">'mensaje'</span>] ?? <span class="st">''</span>);
913
+
914
+ <span class="cm">// 3. Lógica de Negocio (Enviar correo, guardar en DB, etc.)</span>
915
+ <span class="cm">// ...</span>
916
+
917
+ <span class="cm">// 4. Respuesta</span>
918
+ <span class="fn">jsonResponse</span>([<span class="st">'status'</span> =&gt; <span class="st">'success'</span>, <span class="st">'message'</span> =&gt; <span class="st">'Su mensaje ha sido recibido.'</span>], <span class="nu">200</span>);</code></pre>
919
+ </div>
920
+ </li>
921
+ </ol>
922
+
923
+ <div class="alert a-success">
924
+ <strong>✅ ¡Completado!</strong> Ha creado un flujo de formulario totalmente seguro y que cumple con los estándares, con protección XSS, validación CSRF, restricción de métodos HTTP y arquitectura data-js.
925
+ </div>
926
+ </section>
927
+
928
+ </main>
929
+
930
+ <script>
931
+ const sections = document.querySelectorAll('section[id], header[id]');
932
+ const links = document.querySelectorAll('.sidebar nav a');
933
+
934
+ const observer = new IntersectionObserver((entries) => {
935
+ entries.forEach(entry => {
936
+ if (entry.isIntersecting) {
937
+ links.forEach(l => l.classList.remove('active'));
938
+ const active = document.querySelector(`.sidebar nav a[href="#${entry.target.id}"]`);
939
+ if (active) active.classList.add('active');
940
+ }
941
+ });
942
+ }, { rootMargin: '-20% 0px -70% 0px' });
943
+
944
+ sections.forEach(s => observer.observe(s));
945
+ </script>
946
+ </body>
947
+ </html>