eva4j 1.0.14 → 1.0.16

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 (139) hide show
  1. package/.agents/skills/skill-creator/LICENSE.txt +202 -0
  2. package/.agents/skills/skill-creator/SKILL.md +485 -0
  3. package/.agents/skills/skill-creator/agents/analyzer.md +274 -0
  4. package/.agents/skills/skill-creator/agents/comparator.md +202 -0
  5. package/.agents/skills/skill-creator/agents/grader.md +223 -0
  6. package/.agents/skills/skill-creator/assets/eval_review.html +146 -0
  7. package/.agents/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  8. package/.agents/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  9. package/.agents/skills/skill-creator/references/schemas.md +430 -0
  10. package/.agents/skills/skill-creator/scripts/__init__.py +0 -0
  11. package/.agents/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  12. package/.agents/skills/skill-creator/scripts/generate_report.py +326 -0
  13. package/.agents/skills/skill-creator/scripts/improve_description.py +247 -0
  14. package/.agents/skills/skill-creator/scripts/package_skill.py +136 -0
  15. package/.agents/skills/skill-creator/scripts/quick_validate.py +103 -0
  16. package/.agents/skills/skill-creator/scripts/run_eval.py +310 -0
  17. package/.agents/skills/skill-creator/scripts/run_loop.py +328 -0
  18. package/.agents/skills/skill-creator/scripts/utils.py +47 -0
  19. package/AGENTS.md +268 -6
  20. package/COMMAND_EVALUATION.md +15 -16
  21. package/DOMAIN_YAML_GUIDE.md +430 -14
  22. package/FUTURE_FEATURES.md +1627 -1168
  23. package/README.md +461 -13
  24. package/bin/eva4j.js +32 -14
  25. package/config/defaults.json +1 -0
  26. package/docs/commands/EVALUATE_SYSTEM.md +746 -261
  27. package/docs/commands/EXPORT_DIAGRAM.md +153 -0
  28. package/docs/commands/GENERATE_ENTITIES.md +599 -6
  29. package/docs/commands/INDEX.md +7 -0
  30. package/examples/domain-events.yaml +166 -20
  31. package/examples/domain-listeners.yaml +212 -0
  32. package/examples/domain-one-to-many.yaml +1 -0
  33. package/examples/domain-one-to-one.yaml +1 -0
  34. package/examples/domain-ports.yaml +414 -0
  35. package/examples/domain-soft-delete.yaml +47 -44
  36. package/examples/system/notification.yaml +147 -0
  37. package/examples/system/product.yaml +185 -0
  38. package/examples/system/system.yaml +112 -0
  39. package/examples/system-report.html +971 -0
  40. package/examples/system.yaml +46 -3
  41. package/package.json +2 -1
  42. package/src/agents/design-reviewer.agent.md +163 -0
  43. package/src/commands/build.js +714 -0
  44. package/src/commands/create.js +1 -0
  45. package/src/commands/detach.js +149 -108
  46. package/src/commands/evaluate-system.js +234 -8
  47. package/src/commands/export-diagram.js +522 -0
  48. package/src/commands/generate-entities.js +685 -66
  49. package/src/commands/generate-http-exchange.js +2 -0
  50. package/src/commands/generate-kafka-event.js +43 -10
  51. package/src/generators/base-generator.js +18 -6
  52. package/src/generators/postman-generator.js +188 -0
  53. package/src/generators/shared-generator.js +12 -2
  54. package/src/skills/build-system-yaml/SKILL.md +323 -0
  55. package/src/skills/build-system-yaml/references/domain-yaml-spec.md +410 -0
  56. package/src/skills/build-system-yaml/references/module-spec.md +367 -0
  57. package/src/skills/build-system-yaml/references/system-yaml-spec.md +178 -0
  58. package/src/utils/config-manager.js +54 -0
  59. package/src/utils/context-builder.js +1 -0
  60. package/src/utils/domain-diagram.js +192 -0
  61. package/src/utils/domain-validator.js +1020 -0
  62. package/src/utils/fake-data.js +376 -0
  63. package/src/utils/system-validator.js +319 -199
  64. package/src/utils/yaml-to-entity.js +272 -7
  65. package/templates/aggregate/AggregateMapper.java.ejs +3 -2
  66. package/templates/aggregate/AggregateRepository.java.ejs +3 -2
  67. package/templates/aggregate/AggregateRepositoryImpl.java.ejs +6 -5
  68. package/templates/aggregate/AggregateRoot.java.ejs +60 -2
  69. package/templates/aggregate/DomainEventHandler.java.ejs +4 -1
  70. package/templates/aggregate/DomainEventRecord.java.ejs +24 -8
  71. package/templates/aggregate/DomainEventSnapshot.java.ejs +46 -0
  72. package/templates/aggregate/JpaAggregateRoot.java.ejs +6 -0
  73. package/templates/base/docker/Dockerfile.ejs +21 -0
  74. package/templates/base/gradle/build.gradle.ejs +3 -2
  75. package/templates/base/root/AGENTS.md.ejs +306 -45
  76. package/templates/crud/ApplicationMapper.java.ejs +4 -0
  77. package/templates/crud/Controller.java.ejs +4 -4
  78. package/templates/crud/CreateCommand.java.ejs +4 -0
  79. package/templates/crud/CreateCommandHandler.java.ejs +3 -6
  80. package/templates/crud/CreateItemDto.java.ejs +4 -0
  81. package/templates/crud/CreateValueObjectDto.java.ejs +4 -0
  82. package/templates/crud/DeleteCommandHandler.java.ejs +12 -6
  83. package/templates/crud/EndpointsController.java.ejs +6 -6
  84. package/templates/crud/FindByQuery.java.ejs +1 -1
  85. package/templates/crud/FindByQueryHandler.java.ejs +3 -9
  86. package/templates/crud/GetQueryHandler.java.ejs +2 -5
  87. package/templates/crud/ListQuery.java.ejs +1 -1
  88. package/templates/crud/ListQueryHandler.java.ejs +8 -14
  89. package/templates/crud/ScaffoldCommandHandler.java.ejs +2 -4
  90. package/templates/crud/ScaffoldQuery.java.ejs +3 -2
  91. package/templates/crud/ScaffoldQueryHandler.java.ejs +5 -6
  92. package/templates/crud/SubEntityAddCommand.java.ejs +4 -0
  93. package/templates/crud/SubEntityAddCommandHandler.java.ejs +2 -6
  94. package/templates/crud/SubEntityRemoveCommandHandler.java.ejs +2 -7
  95. package/templates/crud/TransitionCommandHandler.java.ejs +2 -6
  96. package/templates/crud/UpdateCommand.java.ejs +4 -0
  97. package/templates/crud/UpdateCommandHandler.java.ejs +2 -5
  98. package/templates/evaluate/report.html.ejs +394 -2
  99. package/templates/kafka-event/DomainEventHandlerMethod.ejs +3 -1
  100. package/templates/kafka-event/Event.java.ejs +9 -0
  101. package/templates/kafka-listener/KafkaController.java.ejs +1 -1
  102. package/templates/kafka-listener/KafkaListenerClass.java.ejs +1 -1
  103. package/templates/kafka-listener/ListenerClass.java.ejs +65 -0
  104. package/templates/kafka-listener/ListenerCommand.java.ejs +31 -0
  105. package/templates/kafka-listener/ListenerCommandHandler.java.ejs +25 -0
  106. package/templates/kafka-listener/ListenerIntegrationEvent.java.ejs +37 -0
  107. package/templates/kafka-listener/ListenerMethod.java.ejs +1 -1
  108. package/templates/kafka-listener/ListenerNestedType.java.ejs +28 -0
  109. package/templates/mock/MockEvent.java.ejs +10 -0
  110. package/templates/mock/MockMessageBrokerImpl.java.ejs +35 -0
  111. package/templates/mock/MockMessageBrokerImplMethod.java.ejs +6 -0
  112. package/templates/mock/SpringEventListener.java.ejs +61 -0
  113. package/templates/ports/PortDomainModel.java.ejs +35 -0
  114. package/templates/ports/PortFeignAdapter.java.ejs +67 -0
  115. package/templates/ports/PortFeignClient.java.ejs +45 -0
  116. package/templates/ports/PortFeignConfig.java.ejs +24 -0
  117. package/templates/ports/PortInterface.java.ejs +45 -0
  118. package/templates/ports/PortNestedType.java.ejs +28 -0
  119. package/templates/ports/PortRequestDto.java.ejs +30 -0
  120. package/templates/ports/PortResponseDto.java.ejs +28 -0
  121. package/templates/postman/Collection.json.ejs +1 -1
  122. package/templates/postman/UnifiedCollection.json.ejs +185 -0
  123. package/templates/shared/annotations/LogAfter.java.ejs +2 -0
  124. package/templates/shared/annotations/LogBefore.java.ejs +2 -0
  125. package/templates/shared/annotations/LogExceptions.java.ejs +2 -0
  126. package/templates/shared/annotations/LogLevel.java.ejs +8 -0
  127. package/templates/shared/annotations/LogTimer.java.ejs +1 -0
  128. package/templates/shared/annotations/Loggable.java.ejs +17 -0
  129. package/templates/shared/configurations/eventPublicationConfig/EventPublicationSchemaConfig.java.ejs +109 -0
  130. package/templates/shared/configurations/loggerConfig/HandlerLogs.java.ejs +120 -32
  131. package/templates/shared/errorMessage/ErrorResponse.java.ejs +23 -0
  132. package/templates/shared/handlerException/HandlerExceptions.java.ejs +99 -79
  133. package/src/commands/generate-system.js +0 -243
  134. package/templates/base/root/skill-build-domain-yaml-references-generate-entities.md.ejs +0 -1103
  135. package/templates/base/root/skill-build-domain-yaml.ejs +0 -292
  136. package/templates/base/root/skill-build-system-yaml.ejs +0 -252
  137. package/templates/shared/errorMessage/ErrorMessage.java.ejs +0 -5
  138. package/templates/shared/errorMessage/FullErrorMessage.java.ejs +0 -9
  139. package/templates/shared/errorMessage/ShortErrorMessage.java.ejs +0 -6
@@ -11,6 +11,7 @@
11
11
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
12
12
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
13
13
  <script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
14
+ <script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js" defer></script>
14
15
  <style>
15
16
  * { box-sizing: border-box; margin: 0; padding: 0; }
16
17
  body { background: #0a0a0f; color: #e8e8f0; font-family: 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif; }
@@ -45,6 +46,7 @@
45
46
  endpoints: ENDPOINTS,
46
47
  flows: FLOWS_LIST,
47
48
  validation: VALIDATION,
49
+ domainValidation: DOMAIN_VALIDATION,
48
50
  generatedAt,
49
51
  } = window.__EVA_DATA__;
50
52
 
@@ -113,7 +115,7 @@
113
115
 
114
116
  // ─── ValidationTab ──────────────────────────────────────────────────────
115
117
  function ValidationTab() {
116
- const [expanded, setExpanded] = useState({ errors: true, warnings: true, ok: false });
118
+ const [expanded, setExpanded] = useState({ errors: true, warnings: true, info: false, ok: false });
117
119
 
118
120
  const score = VALIDATION.score;
119
121
  const scoreColor = score > 80 ? C.green : score > 60 ? C.gold : C.accent;
@@ -178,6 +180,7 @@
178
180
  {[
179
181
  { label: "Errores", count: VALIDATION.errors.length, color: C.accent, icon: "🔴" },
180
182
  { label: "Advertencias", count: VALIDATION.warnings.length, color: C.gold, icon: "🟡" },
183
+ { label: "Info", count: (VALIDATION.info || []).length, color: C.blue, icon: "🔵" },
181
184
  { label: "Validados", count: VALIDATION.ok.length, color: C.green, icon: "🟢" },
182
185
  ].map(s => (
183
186
  <div key={s.label} style={{
@@ -194,6 +197,7 @@
194
197
 
195
198
  <Section title="Errores críticos" items={VALIDATION.errors} color={C.accent} icon="🔴" sectionKey="errors" />
196
199
  <Section title="Advertencias" items={VALIDATION.warnings} color={C.gold} icon="🟡" sectionKey="warnings" />
200
+ <Section title="Notas informativas" items={VALIDATION.info || []} color={C.blue} icon="🔵" sectionKey="info" />
197
201
  <Section title="Validaciones pasadas" items={VALIDATION.ok} color={C.green} icon="🟢" sectionKey="ok" />
198
202
  </div>
199
203
  );
@@ -827,7 +831,7 @@
827
831
  background: C.surface, border: `1px solid ${C.border}`,
828
832
  borderRadius: 12, overflow: "hidden",
829
833
  }}>
830
- <div ref={containerRef} style={{ width: "100%", height: 520 }} />
834
+ <div ref={containerRef} style={{ width: "100%", height: 720 }} />
831
835
  {showOverlay && (
832
836
  <div style={{
833
837
  position: "absolute", bottom: 12, left: 12,
@@ -885,7 +889,393 @@
885
889
  </div>
886
890
  );
887
891
  }
892
+ // ─── Mermaid helper ──────────────────────────────────────────────────────────────
893
+ let _mermaidReady = false;
894
+ function ensureMermaid() {
895
+ if (!_mermaidReady && typeof window.mermaid !== 'undefined') {
896
+ window.mermaid.initialize({
897
+ startOnLoad: false,
898
+ theme: 'dark',
899
+ themeVariables: {
900
+ background: '#0a0a0f',
901
+ primaryColor: '#1e1e2e',
902
+ primaryBorderColor: '#4a4a8a',
903
+ lineColor: '#8c8caa',
904
+ fontFamily: "'JetBrains Mono', monospace",
905
+ fontSize: '13px',
906
+ },
907
+ });
908
+ _mermaidReady = true;
909
+ }
910
+ return _mermaidReady;
911
+ }
912
+
913
+ // ─── DiagramPanel ────────────────────────────────────────────────────────────────
914
+ function DiagramPanel({ moduleKey, diagramText }) {
915
+ const containerRef = useRef(null);
916
+ const wrapperRef = useRef(null);
917
+ const [renderState, setRenderState] = useState('idle');
918
+ const [errorMsg, setErrorMsg] = useState('');
919
+ const [wrapperJustify, setWrapperJustify] = useState('flex-start');
920
+
921
+ useEffect(() => {
922
+ if (!diagramText) return;
923
+ let cancelled = false;
924
+ setRenderState('loading');
925
+ (async () => {
926
+ try {
927
+ // Wait up to 4s for mermaid to load (deferred script)
928
+ let waited = 0;
929
+ while (!ensureMermaid() && waited < 4000) {
930
+ await new Promise((r) => setTimeout(r, 100));
931
+ waited += 100;
932
+ }
933
+ if (!ensureMermaid()) throw new Error('Mermaid no está disponible');
934
+ if (cancelled) return;
935
+ const id = 'mmd-' + moduleKey.replace(/[^a-zA-Z0-9]/g, '-') + '-' + Date.now();
936
+ const { svg } = await window.mermaid.render(id, diagramText);
937
+ if (cancelled || !containerRef.current) return;
938
+ containerRef.current.innerHTML = svg;
939
+ const svgEl = containerRef.current.querySelector('svg');
940
+ if (svgEl) {
941
+ svgEl.removeAttribute('height');
942
+ svgEl.removeAttribute('width');
943
+
944
+ // Measure intrinsic width from viewBox to decide sizing strategy
945
+ const vb = svgEl.getAttribute('viewBox');
946
+ const intrinsicW = vb ? parseFloat(vb.split(/\s+/)[2]) : 0;
947
+ const containerW = wrapperRef.current ? wrapperRef.current.clientWidth - 40 : 0;
948
+
949
+ if (intrinsicW > 0 && intrinsicW < containerW * 0.65) {
950
+ // Small diagram (1-2 classes): render at natural size and center
951
+ svgEl.style.width = intrinsicW + 'px';
952
+ svgEl.style.maxWidth = '100%';
953
+ setWrapperJustify('center');
954
+ } else {
955
+ // Large diagram: expand to fill; overflow: auto handles horizontal scroll
956
+ svgEl.style.width = Math.max(intrinsicW, containerW) + 'px';
957
+ svgEl.style.maxWidth = 'none';
958
+ setWrapperJustify('flex-start');
959
+ }
960
+ }
961
+ setRenderState('done');
962
+ } catch (err) {
963
+ if (!cancelled) {
964
+ setErrorMsg(err.message || String(err));
965
+ setRenderState('error');
966
+ }
967
+ }
968
+ })();
969
+ return () => { cancelled = true; };
970
+ }, [moduleKey, diagramText]);
971
+
972
+ if (!diagramText) {
973
+ return (
974
+ <div style={{ padding: 40, textAlign: 'center', color: C.textMuted, fontSize: 13 }}>
975
+ No hay diagrama disponible para este módulo
976
+ </div>
977
+ );
978
+ }
979
+ return (
980
+ <div
981
+ ref={wrapperRef}
982
+ style={{
983
+ background: '#0d0d15', borderRadius: 10, padding: 20,
984
+ overflow: 'auto', border: `1px solid ${C.border}`, minHeight: 200,
985
+ display: 'flex', justifyContent: wrapperJustify,
986
+ }}
987
+ >
988
+ {renderState === 'loading' && (
989
+ <div style={{ color: C.textMuted, fontSize: 13, textAlign: 'center', padding: 30, width: '100%' }}>
990
+ ⏳ Renderizando diagrama...
991
+ </div>
992
+ )}
993
+ {renderState === 'error' && (
994
+ <div style={{ color: C.accent, fontSize: 12, padding: 10, fontFamily: "'JetBrains Mono', monospace" }}>
995
+ ⚠ {errorMsg}
996
+ </div>
997
+ )}
998
+ <div ref={containerRef} style={{ display: renderState === 'done' ? 'block' : 'none' }} />
999
+ </div>
1000
+ );
1001
+ }
1002
+
1003
+ // ─── DomainTab ──────────────────────────────────────────────────────────────────
1004
+ function DomainTab() {
1005
+ const [expandedChecks, setExpandedChecks] = useState({});
1006
+ const [selectedModule, setSelectedModule] = useState('all');
1007
+ const [view, setView] = useState('findings');
1008
+ if (!DOMAIN_VALIDATION) return null;
1009
+
1010
+ const { summary, categories, diagrams } = DOMAIN_VALIDATION;
1011
+
1012
+ // Collect all unique module names that have at least one finding
1013
+ const allModules = [...new Set(
1014
+ categories.flatMap(cat =>
1015
+ cat.checks.flatMap(check => check.findings.map(f => f.module))
1016
+ )
1017
+ )].sort();
1018
+
1019
+ const SEV_COLOR = {
1020
+ error: C.accent,
1021
+ warning: C.gold,
1022
+ info: C.blue,
1023
+ ok: C.green,
1024
+ };
1025
+ const SEV_LABEL = {
1026
+ error: 'Error',
1027
+ warning: 'Warning',
1028
+ info: 'Info',
1029
+ ok: 'OK',
1030
+ };
1031
+ const SEV_ICON = {
1032
+ error: '🔴',
1033
+ warning: '🟡',
1034
+ info: '🔵',
1035
+ ok: '🟢',
1036
+ };
1037
+
1038
+ function toggleCheck(id) {
1039
+ setExpandedChecks(prev => ({ ...prev, [id]: !prev[id] }));
1040
+ }
1041
+
1042
+ function changeModule(mod) {
1043
+ setSelectedModule(mod);
1044
+ setExpandedChecks({});
1045
+ }
1046
+
1047
+ return (
1048
+ <div style={{ animation: 'fadeIn 0.25s ease' }}>
1049
+
1050
+ {/* Summary bar */}
1051
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12, marginBottom: 20 }}>
1052
+ {[
1053
+ { label: 'Errors', count: summary.errors, color: C.accent },
1054
+ { label: 'Warnings', count: summary.warnings, color: C.gold },
1055
+ { label: 'Info', count: summary.info, color: C.blue },
1056
+ { label: 'OK', count: summary.ok, color: C.green },
1057
+ ].map(stat => (
1058
+ <div key={stat.label} style={{
1059
+ background: C.surface, border: `1px solid ${stat.color}44`,
1060
+ borderRadius: 10, padding: '16px 20px', textAlign: 'center',
1061
+ }}>
1062
+ <div style={{ fontSize: 28, fontWeight: 900, color: stat.color }}>{stat.count}</div>
1063
+ <div style={{ fontSize: 12, color: C.textMuted, marginTop: 4 }}>{stat.label}</div>
1064
+ </div>
1065
+ ))}
1066
+ </div>
888
1067
 
1068
+ {/* Module filter */}
1069
+ <div style={{
1070
+ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 20,
1071
+ padding: '12px 16px', background: C.surface,
1072
+ border: `1px solid ${C.border}`, borderRadius: 8,
1073
+ }}>
1074
+ <span style={{ fontSize: 12, color: C.textMuted, fontWeight: 600, flexShrink: 0 }}>Filtrar por módulo</span>
1075
+ <select
1076
+ value={selectedModule}
1077
+ onChange={e => changeModule(e.target.value)}
1078
+ style={{
1079
+ background: C.bg, color: C.text,
1080
+ border: `1px solid ${selectedModule !== 'all' ? C.blue : C.borderBright}`,
1081
+ borderRadius: 6, padding: '6px 32px 6px 12px', fontSize: 13,
1082
+ cursor: 'pointer', fontFamily: 'inherit', outline: 'none',
1083
+ appearance: 'none', WebkitAppearance: 'none',
1084
+ backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%238c8caa'/%3E%3C/svg%3E")`,
1085
+ backgroundRepeat: 'no-repeat', backgroundPosition: 'right 10px center',
1086
+ }}
1087
+ >
1088
+ <option value="all">Todos los módulos ({allModules.length})</option>
1089
+ {allModules.map(m => (
1090
+ <option key={m} value={m}>{m}</option>
1091
+ ))}
1092
+ </select>
1093
+ {selectedModule !== 'all' && (
1094
+ <button
1095
+ onClick={() => changeModule('all')}
1096
+ style={{
1097
+ background: C.accent + '22', color: C.accent,
1098
+ border: `1px solid ${C.accent}44`, borderRadius: 6,
1099
+ padding: '4px 12px', fontSize: 12, cursor: 'pointer',
1100
+ fontFamily: 'inherit', fontWeight: 600,
1101
+ }}
1102
+ >
1103
+ ✕ limpiar
1104
+ </button>
1105
+ )}
1106
+ {selectedModule !== 'all' && (
1107
+ <span style={{ color: C.blue, fontSize: 12, marginLeft: 4 }}>
1108
+ Mostrando hallazgos de <strong style={{ color: C.text }}>{selectedModule}</strong>
1109
+ </span>
1110
+ )}
1111
+ </div>
1112
+
1113
+ {/* View toggle pill (only rendered when diagrams are available) */}
1114
+ {diagrams && Object.keys(diagrams).length > 0 && (
1115
+ <div style={{ display: 'flex', alignItems: 'center', gap: 4, marginBottom: 20 }}>
1116
+ {[{ id: 'findings', label: '📋 Hallazgos' }, { id: 'diagram', label: '🗺 Diagrama' }].map(v => (
1117
+ <button
1118
+ key={v.id}
1119
+ onClick={() => setView(v.id)}
1120
+ style={{
1121
+ background: view === v.id ? C.purple + '33' : 'transparent',
1122
+ color: view === v.id ? C.purple : C.textMuted,
1123
+ border: `1px solid ${view === v.id ? C.purple + '66' : C.border}`,
1124
+ borderRadius: 6, padding: '6px 16px', fontSize: 12,
1125
+ fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit',
1126
+ transition: 'all 0.15s',
1127
+ }}
1128
+ >{v.label}</button>
1129
+ ))}
1130
+ </div>
1131
+ )}
1132
+
1133
+ {/* Diagram view */}
1134
+ {view === 'diagram' && (
1135
+ <div>
1136
+ {selectedModule === 'all' ? (
1137
+ <div style={{ padding: 32, textAlign: 'center', background: C.surface, borderRadius: 10, border: `1px solid ${C.border}`, color: C.textMuted, fontSize: 13 }}>
1138
+ Selecciona un módulo en el filtro de arriba para ver su diagrama de clases
1139
+ </div>
1140
+ ) : (
1141
+ <DiagramPanel moduleKey={selectedModule} diagramText={diagrams && diagrams[selectedModule]} />
1142
+ )}
1143
+ </div>
1144
+ )}
1145
+
1146
+ {/* Category cards */}
1147
+ {view === 'findings' && categories.map(cat => {
1148
+ // Apply module filter to each check's findings
1149
+ const filteredChecks = cat.checks.map(check => ({
1150
+ ...check,
1151
+ visibleFindings: selectedModule === 'all'
1152
+ ? check.findings
1153
+ : check.findings.filter(f => f.module === selectedModule),
1154
+ }));
1155
+ // If filtering and no check in this category has visible findings, skip the card
1156
+ const anyVisible = filteredChecks.some(c => c.visibleFindings.length > 0 || c.severity === 'ok');
1157
+
1158
+ return (
1159
+ <div key={cat.id} style={{
1160
+ marginBottom: 20, border: `1px solid ${C.border}`,
1161
+ borderRadius: 10, overflow: 'hidden',
1162
+ opacity: selectedModule !== 'all' && !filteredChecks.some(c => c.visibleFindings.length > 0) ? 0.45 : 1,
1163
+ transition: 'opacity 0.2s',
1164
+ }}>
1165
+ {/* Category header */}
1166
+ <div style={{
1167
+ background: C.surface, padding: '14px 18px',
1168
+ borderBottom: `1px solid ${C.border}`,
1169
+ display: 'flex', alignItems: 'flex-start', gap: 12,
1170
+ }}>
1171
+ <span style={{
1172
+ background: C.purple + '33', color: C.purple,
1173
+ border: `1px solid ${C.purple}44`, borderRadius: 6,
1174
+ padding: '2px 10px', fontSize: 12, fontWeight: 700,
1175
+ fontFamily: "'JetBrains Mono', monospace", flexShrink: 0, marginTop: 2,
1176
+ }}>{cat.id}</span>
1177
+ <div style={{ flex: 1 }}>
1178
+ <div style={{ fontWeight: 700, fontSize: 14, color: C.text }}>{cat.label}</div>
1179
+ <div style={{ fontSize: 12, color: C.textMuted, marginTop: 3 }}>{cat.description}</div>
1180
+ </div>
1181
+ {selectedModule !== 'all' && (
1182
+ <span style={{ fontSize: 11, color: C.textMuted, flexShrink: 0, marginTop: 4 }}>
1183
+ {filteredChecks.reduce((n, c) => n + c.visibleFindings.length, 0)} hallazgo(s)
1184
+ </span>
1185
+ )}
1186
+ </div>
1187
+
1188
+ {/* Checks list */}
1189
+ <div>
1190
+ {filteredChecks.map((check, idx) => {
1191
+ const color = SEV_COLOR[check.severity] || C.textMuted;
1192
+ const isExpanded = expandedChecks[check.id];
1193
+ const hasFindings = check.visibleFindings.length > 0;
1194
+ return (
1195
+ <div key={check.id} style={{
1196
+ borderBottom: idx < filteredChecks.length - 1 ? `1px solid ${C.border}` : 'none',
1197
+ }}>
1198
+ {/* Check row */}
1199
+ <div
1200
+ onClick={() => hasFindings && toggleCheck(check.id)}
1201
+ style={{
1202
+ display: 'flex', alignItems: 'center', gap: 12,
1203
+ padding: '11px 18px',
1204
+ cursor: hasFindings ? 'pointer' : 'default',
1205
+ background: isExpanded ? C.surfaceHover : 'transparent',
1206
+ transition: 'background 0.15s',
1207
+ }}
1208
+ >
1209
+ <span style={{
1210
+ fontFamily: "'JetBrains Mono', monospace",
1211
+ fontSize: 11, color: C.textMuted, flexShrink: 0, width: 60,
1212
+ }}>{check.id}</span>
1213
+
1214
+ <span style={{
1215
+ background: color + '22', color, border: `1px solid ${color}44`,
1216
+ borderRadius: 4, padding: '1px 8px', fontSize: 11, fontWeight: 700,
1217
+ flexShrink: 0, minWidth: 64, textAlign: 'center',
1218
+ }}>
1219
+ {SEV_ICON[check.severity]} {SEV_LABEL[check.severity]}
1220
+ </span>
1221
+
1222
+ <span style={{ fontSize: 13, color: C.textDim, flex: 1 }}>{check.label}</span>
1223
+
1224
+ {hasFindings && (
1225
+ <span style={{
1226
+ background: color + '22', color, borderRadius: 20,
1227
+ padding: '2px 10px', fontSize: 11, fontWeight: 700, flexShrink: 0,
1228
+ }}>
1229
+ {check.visibleFindings.length}
1230
+ {selectedModule === 'all' ? '' : ` / ${check.findings.length}`}
1231
+ {' '}hallazgo{check.visibleFindings.length !== 1 ? 's' : ''}
1232
+ </span>
1233
+ )}
1234
+
1235
+ {hasFindings && (
1236
+ <span style={{ color: C.textMuted, fontSize: 12, flexShrink: 0, transition: 'transform 0.15s', transform: isExpanded ? 'rotate(90deg)' : 'none' }}>&#9654;</span>
1237
+ )}
1238
+ </div>
1239
+
1240
+ {/* Findings panel */}
1241
+ {isExpanded && hasFindings && (
1242
+ <div style={{
1243
+ background: '#0d0d15', borderTop: `1px solid ${C.border}`,
1244
+ padding: '0 18px 12px 18px',
1245
+ }}>
1246
+ {check.visibleFindings.map((f, fi) => (
1247
+ <div key={fi} style={{
1248
+ padding: '10px 0',
1249
+ borderBottom: fi < check.visibleFindings.length - 1 ? `1px solid ${C.border}` : 'none',
1250
+ display: 'flex', alignItems: 'flex-start', gap: 12,
1251
+ }}>
1252
+ <span style={{
1253
+ background: color + '22', color,
1254
+ border: `1px solid ${color}44`, borderRadius: 4,
1255
+ padding: '1px 8px', fontSize: 11, fontWeight: 600,
1256
+ flexShrink: 0, marginTop: 1,
1257
+ fontFamily: "'JetBrains Mono', monospace",
1258
+ }}>{f.module}</span>
1259
+ <div>
1260
+ <div style={{ fontSize: 13, color: C.text }}>{f.message}</div>
1261
+ {f.context && (
1262
+ <div style={{ fontSize: 11, color: C.textMuted, marginTop: 4 }}>{f.context}</div>
1263
+ )}
1264
+ </div>
1265
+ </div>
1266
+ ))}
1267
+ </div>
1268
+ )}
1269
+ </div>
1270
+ );
1271
+ })}
1272
+ </div>
1273
+ </div>
1274
+ );
1275
+ })}
1276
+ </div>
1277
+ );
1278
+ }
889
1279
  // ─── App root ───────────────────────────────────────────────────────────
890
1280
  function App() {
891
1281
  const [tab, setTab] = useState("validation");
@@ -895,6 +1285,7 @@
895
1285
  { id: "flows", label: "Simulador de flujos", icon: "▶" },
896
1286
  { id: "architecture", label: "Arquitectura", icon: "🗺️" },
897
1287
  { id: "diagram", label: "Diagrama", icon: "◈" },
1288
+ ...(DOMAIN_VALIDATION ? [{ id: "domain", label: "Dominio", icon: "🏛️" }] : []),
898
1289
  ];
899
1290
 
900
1291
  const sys = window.__EVA_DATA__;
@@ -958,6 +1349,7 @@
958
1349
  {tab === "flows" && <FlowSimulator />}
959
1350
  {tab === "architecture" && <ArchitectureTab />}
960
1351
  {tab === "diagram" && <DiagramTab />}
1352
+ {tab === "domain" && DOMAIN_VALIDATION && <DomainTab />}
961
1353
  </div>
962
1354
  </div>
963
1355
  );
@@ -1 +1,3 @@
1
- messageBroker.publish<%= eventClassName %>(new <%= eventClassName %>(<% if (domainEventFields && domainEventFields.length > 0) { domainEventFields.forEach(function(field, idx) { %>event.get<%= field.name.charAt(0).toUpperCase() + field.name.slice(1) %>()<%= idx < domainEventFields.length - 1 ? ', ' : '' %><% }); } %>));
1
+ <%
2
+ const _aggrIdField = aggregateName ? aggregateName.charAt(0).toLowerCase() + aggregateName.slice(1) + 'Id' : null;
3
+ %>messageBroker.publish<%= eventClassName %>(new <%= eventClassName %>(<% if (domainEventFields && domainEventFields.length > 0) { domainEventFields.forEach(function(field, idx) { %><%= (_aggrIdField && field.name === _aggrIdField) ? 'event.getAggregateId()' : 'event.get' + field.name.charAt(0).toUpperCase() + field.name.slice(1) + '()' %><%= idx < domainEventFields.length - 1 ? ', ' : '' %><% }); } %>));
@@ -3,6 +3,12 @@ package <%= packageName %>.<%= moduleName %>.application.events;
3
3
  <% const needsLocalDate = eventFields && eventFields.some(f => f.javaType === 'LocalDate' || f.javaType === 'LocalDateTime' || f.javaType === 'LocalTime'); %>
4
4
  <% const needsUUID = eventFields && eventFields.some(f => f.javaType === 'UUID'); %>
5
5
  <% const needsList = eventFields && eventFields.some(f => f.isCollection); %>
6
+ <%
7
+ const _STANDARD_TYPES = new Set(['String','Integer','Long','Double','Float','Boolean','BigDecimal','LocalDate','LocalDateTime','LocalTime','Instant','UUID']);
8
+ const _customElementTypes = eventFields
9
+ ? [...new Set(eventFields.filter(f => f.isCollection && f.collectionElementType && !_STANDARD_TYPES.has(f.collectionElementType)).map(f => f.collectionElementType))]
10
+ : [];
11
+ %>
6
12
  <% if (needsBigDecimal) { %>
7
13
  import java.math.BigDecimal;
8
14
  <% } %>
@@ -16,6 +22,9 @@ import java.util.UUID;
16
22
  <% if (needsList) { %>
17
23
  import java.util.List;
18
24
  <% } %>
25
+ <% _customElementTypes.forEach(typeName => { %>
26
+ import <%= packageName %>.<%= moduleName %>.domain.models.events.<%- typeName %>;
27
+ <% }); %>
19
28
 
20
29
  /**
21
30
  * Integration Event — broker-side projection of the corresponding domain event.
@@ -23,7 +23,7 @@ public class KafkaController {
23
23
  this.useCaseMediator = useCaseMediator;
24
24
  }
25
25
 
26
- @KafkaListener(topics = "<%= topicSpringProperty %>")
26
+ @KafkaListener(topics = "<%= topicSpringProperty %>", groupId = "${spring.application.name}-<%= moduleName %>-group")
27
27
  void <%= methodName %>(EventEnvelope<Map<String, Object>> event, Acknowledgment ack) {
28
28
  // TODO: Implement event processing logic
29
29
  // Example: useCaseMediator.dispatch(new YourCommand(event.data()));
@@ -26,7 +26,7 @@ public class <%= listenerClassName %> {
26
26
  this.useCaseMediator = useCaseMediator;
27
27
  }
28
28
 
29
- @KafkaListener(topics = "<%= topicSpringProperty %>")
29
+ @KafkaListener(topics = "<%= topicSpringProperty %>", groupId = "${spring.application.name}-<%= moduleName %>-group")
30
30
  public void handle(EventEnvelope<Map<String, Object>> event, Acknowledgment ack) {
31
31
  // TODO: Implement event processing logic
32
32
  // Example: useCaseMediator.dispatch(new YourCommand(event.data()));
@@ -0,0 +1,65 @@
1
+ package <%= packageName %>.<%= moduleName %>.infrastructure.kafkaListener;
2
+
3
+ import <%= packageName %>.<%= moduleName %>.application.commands.<%= commandClassName %>;
4
+ import <%= packageName %>.shared.infrastructure.configurations.useCaseConfig.UseCaseMediator;
5
+ import <%= packageName %>.shared.infrastructure.eventEnvelope.EventEnvelope;
6
+
7
+ import com.fasterxml.jackson.databind.ObjectMapper;
8
+ import org.springframework.beans.factory.annotation.Value;
9
+ import org.springframework.kafka.annotation.KafkaListener;
10
+ import org.springframework.kafka.support.Acknowledgment;
11
+ import org.springframework.stereotype.Component;
12
+
13
+ import java.util.Map;
14
+ <% const hasLists = fields && fields.some(f => f.javaType && f.javaType.startsWith('List')); %>
15
+ <% if (hasLists) { %>import java.util.List;
16
+ <% } %><% const needsBigDecimal = fields && fields.some(f => f.javaType === 'BigDecimal'); %>
17
+ <% const needsLocalDate = fields && fields.some(f => ['LocalDate','LocalDateTime','LocalTime'].includes(f.javaType)); %>
18
+ <% const needsInstant = fields && fields.some(f => f.javaType === 'Instant'); %>
19
+ <% const needsUUID = fields && fields.some(f => f.javaType === 'UUID'); %>
20
+ <% if (needsBigDecimal) { %>import java.math.BigDecimal;
21
+ <% } %><% if (needsLocalDate) { %>import java.time.LocalDate;
22
+ import java.time.LocalDateTime;
23
+ import java.time.LocalTime;
24
+ <% } %><% if (needsInstant) { %>import java.time.Instant;
25
+ <% } %><% if (needsUUID) { %>import java.util.UUID;
26
+ <% } %><% (nestedTypes || []).forEach(nt => { %>import <%= packageName %>.<%= moduleName %>.application.events.<%= nt.name %>;
27
+ <% }); %>
28
+ /**
29
+ * Kafka listener for topic <%= topicConstant %>.
30
+ * Consumes events produced by: <%= producer %>.
31
+ * Dispatches to use case: <%= useCase %>.
32
+ */
33
+ @Component("<%= moduleName %>.<%= listenerClassName %>")
34
+ public class <%= listenerClassName %> {
35
+
36
+ private final UseCaseMediator useCaseMediator;
37
+ private final ObjectMapper objectMapper;
38
+
39
+ @Value("<%= topicSpringProperty %>")
40
+ private String <%= topicVariableName %>Topic;
41
+
42
+ public <%= listenerClassName %>(UseCaseMediator useCaseMediator, ObjectMapper objectMapper) {
43
+ this.useCaseMediator = useCaseMediator;
44
+ this.objectMapper = objectMapper;
45
+ }
46
+
47
+ @KafkaListener(topics = "<%= topicSpringProperty %>", groupId = "${spring.application.name}-<%= moduleName %>-group")
48
+ public void handle(EventEnvelope<Map<String, Object>> event, Acknowledgment ack) {
49
+ <% (fields || []).forEach(f => { %><%
50
+ const listMatch = f.javaType.match(/^List<(.+)>$/);
51
+ if (listMatch) { %>
52
+ <%- f.javaType %> <%= f.name %> = objectMapper.convertValue(
53
+ event.data().get("<%= f.name %>"),
54
+ objectMapper.getTypeFactory().constructCollectionType(List.class, <%= listMatch[1] %>.class));
55
+ <% } else { %>
56
+ <%- f.javaType %> <%= f.name %> = objectMapper.convertValue(event.data().get("<%= f.name %>"), <%- f.javaType %>.class);
57
+ <% } %><% }); %>
58
+ useCaseMediator.dispatch(new <%= commandClassName %>(
59
+ <% (fields || []).forEach((f, i) => { %>
60
+ <%= f.name %><%= i < fields.length - 1 ? ',' : '' %>
61
+ <% }); %>
62
+ ));
63
+ ack.acknowledge();
64
+ }
65
+ }
@@ -0,0 +1,31 @@
1
+ package <%= packageName %>.<%= moduleName %>.application.commands;
2
+
3
+ import <%= packageName %>.shared.domain.interfaces.Command;
4
+ <% const needsBigDecimal = fields && fields.some(f => f.javaType === 'BigDecimal'); %>
5
+ <% const needsLocalDate = fields && fields.some(f => ['LocalDate','LocalDateTime','LocalTime'].includes(f.javaType)); %>
6
+ <% const needsInstant = fields && fields.some(f => f.javaType === 'Instant'); %>
7
+ <% const needsUUID = fields && fields.some(f => f.javaType === 'UUID'); %>
8
+ <% const needsList = fields && fields.some(f => f.javaType && f.javaType.startsWith('List')); %>
9
+ <% if (needsBigDecimal) { %>import java.math.BigDecimal;
10
+ <% } %><% if (needsLocalDate) { %>import java.time.LocalDate;
11
+ import java.time.LocalDateTime;
12
+ import java.time.LocalTime;
13
+ <% } %><% if (needsInstant) { %>import java.time.Instant;
14
+ <% } %><% if (needsUUID) { %>import java.util.UUID;
15
+ <% } %><% if (needsList) { %>import java.util.List;
16
+ <% } %><% (nestedTypes || []).forEach(nt => { %>import <%= packageName %>.<%= moduleName %>.application.events.<%= nt.name %>;
17
+ <% }); %>
18
+ /**
19
+ * Command dispatched when <%= event %> is received from topic <%= topicConstant %>.
20
+ * Produced by: <%= producer %>.
21
+ * Handled by use case: <%= useCase %>.
22
+ */
23
+ public record <%= commandClassName %>(
24
+ <% if (fields && fields.length > 0) { %>
25
+ <% fields.forEach((f, i) => { %>
26
+ <%- f.javaType %> <%= f.name %><%= i < fields.length - 1 ? ',' : '' %>
27
+ <% }); %>
28
+ <% } else { %>
29
+ // TODO: Add command fields
30
+ <% } %>
31
+ ) implements Command {}
@@ -0,0 +1,25 @@
1
+ package <%= packageName %>.<%= moduleName %>.application.usecases;
2
+
3
+ import <%= packageName %>.<%= moduleName %>.application.commands.<%= commandClassName %>;
4
+ import <%= packageName %>.shared.domain.annotations.ApplicationComponent;
5
+ import <%= packageName %>.shared.domain.annotations.LogExceptions;
6
+ import <%= packageName %>.shared.domain.interfaces.CommandHandler;
7
+
8
+ /**
9
+ * Handles <%= commandClassName %> dispatched when <%= event %> is received.
10
+ * Produced by: <%= producer %>.
11
+ */
12
+ @ApplicationComponent
13
+ public class <%= useCase %>CommandHandler implements CommandHandler<<%= commandClassName %>> {
14
+
15
+ public <%= useCase %>CommandHandler() {
16
+
17
+ }
18
+
19
+ @Override
20
+ @LogExceptions
21
+ public void handle(<%= commandClassName %> command) {
22
+ // TODO: implement <%= useCase %> logic
23
+ }
24
+
25
+ }